/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is dual-licensed under either the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree or the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree. You may select, at your option, one of the
 * above-listed licenses.
 */

package com.facebook.buck.jvm.java.abi;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;

import com.facebook.buck.cd.model.java.AbiGenerationMode;
import com.facebook.buck.core.filesystems.AbsPath;
import com.facebook.buck.jvm.java.JarDumper;
import com.facebook.buck.jvm.java.testutil.compiler.TestCompiler;
import com.facebook.buck.jvm.kotlin.testutil.compiler.KotlinTestCompiler;
import com.facebook.buck.util.environment.EnvVariablesProvider;
import com.facebook.buck.util.environment.Platform;
import com.facebook.buck.util.unarchive.Unzip;
import com.facebook.buck.util.zip.DeterministicManifest;
import com.facebook.buck.util.zip.JarBuilder;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.CharStreams;
import com.google.common.io.Files;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.Callable;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.objectweb.asm.ClassReader;

// The stub source is easier to read as long lines, so...
// CHECKSTYLE.OFF: LineLengthCheck
@RunWith(Parameterized.class)
public class StubJarTest {

  // Test a stub generated by stripping a full jar
  private static final String MODE_JAR_BASED = "JAR_BASED";

  // Test a stub generated from source
  private static final String MODE_SOURCE_BASED = "SOURCE_BASED";

  // Test a stub generated from source, with dependencies missing
  private static final String MODE_SOURCE_BASED_MISSING_DEPS = "SOURCE_BASED_MISSING_DEPS";

  @Parameterized.Parameter(0)
  public String testingMode;

  @Parameterized.Parameter(1)
  public String sourceVersion;

  @Parameterized.Parameters(name = "{0}-source={1}")
  public static Object[][] getParameters() {
    List<String> modes = List.of(MODE_JAR_BASED, MODE_SOURCE_BASED, MODE_SOURCE_BASED_MISSING_DEPS);
    List<String> supportedSourceVersions = List.of("8", "11");
    return Lists.cartesianProduct(modes, supportedSourceVersions).stream()
        .map(List::toArray)
        .collect(Collectors.toList())
        .toArray(new Object[][] {});
  }

  public boolean isKotlin21() {
    return EnvVariablesProvider.getSystemEnv().get("KOTLIN_VERSION").equals("2.1.0");
  }

  public boolean isKotlin20() {
    return EnvVariablesProvider.getSystemEnv().get("KOTLIN_VERSION").equals("2.0.20");
  }

  private static final ImmutableSortedSet<Path> EMPTY_CLASSPATH = ImmutableSortedSet.of();

  @Rule public TemporaryFolder temp = new TemporaryFolder();

  private Tester tester = new Tester(Language.JAVA);

  @Before
  public void createTempFilesystem() throws IOException {}

  @Test
  public void emptyClass() throws IOException {
    tester
        .setSourceFile("A.java", "package com.example.buck; public class A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile("B.java", "package com.example.buck; public class B extends A {}")
        .testCanCompile();
  }

  @Test
  public void emptyKotlinClass() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T94515328
      // Kotlin Metadata is different in Source and Jar -based modes (xi=48)
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u000c\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u000c\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\"})";
    tester
        .setSourceFile("A.kt", "package com.example.buck open class A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile("B.kt", "package com.example.buck class B: A() {}")
        .testCanCompile();
  }

  @Test
  public void emptyClassWithAnnotation() throws IOException {
    tester
        .setSourceFile("A.java", "package com.example.buck; @Deprecated public class A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// DEPRECATED",
            "// access flags 0x20021",
            "public class com/example/buck/A {",
            "",
            "",
            "  @Ljava/lang/Deprecated;()",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void testDeprecatedViaDocComment() throws IOException {
    tester
        .setSourceFile(
            "A.java", "package com.example.buck;", "/** @deprecated */", "public class A { }")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// DEPRECATED",
            "// access flags 0x20021",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void classWithTwoMethods() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public String toString() { return null; }",
            "  public void eatCake() {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public toString()Ljava/lang/String;",
            "",
            "  // access flags 0x1",
            "  public eatCake()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void kotlinClassWithInlineMethod() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T94515328
      // Kotlin Metadata is different in Source and Jar -based modes (xi=48)
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0016\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0009\\u0010\\u0004\\u001a\\u00020\\u0005H\\u0086\\u0008J\\u0006\\u0010\\u0006\\u001a\\u00020\\u0007\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"getString\", \"\","
            + " \"someOtherMethod\", \"\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0016\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0009\\u0010\\u0004\\u001a\\u00020\\u0005H\\u0086\\u0008J\\u0006\\u0010\\u0006\\u001a\\u00020\\u0007\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"getString\", \"\","
            + " \"someOtherMethod\", \"\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "open class A {",
            "  inline fun getString(): String { return \"test\" }",
            "  fun someOtherMethod() {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  public final getString()Ljava/lang/String;",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "   L0",
            "    ICONST_0",
            "    ISTORE 1",
            "   L1",
            "    LINENUMBER 3 L1",
            "    LDC \"test\"",
            "    ARETURN",
            "   L2",
            "    LOCALVARIABLE $i$f$getString I L1 L2 1",
            "    LOCALVARIABLE this Lcom/example/buck/A; L0 L2 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x11",
            "  public final someOtherMethod()V",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "B.kt",
            "package com.example.buck",
            "class B {",
            "fun useInline(): String { return A().getString() }",
            "}")
        .testCanCompile();
  }

  @Test
  public void kotlinClassWithInlineMethodThatUsesInternalMethodWithPublishedApi()
      throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T94515328
      // Kotlin Metadata is different in Source and Jar -based modes (xi=48)
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0012\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0000\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0009\\u0010\\u0004\\u001a\\u00020\\u0005H\\u0086\\u0008J\\u0008\\u0010\\u0006\\u001a\\u00020\\u0005H\\u0001\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"getString\", \"\","
            + " \"getPublishedString\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0012\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0000\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0009\\u0010\\u0004\\u001a\\u00020\\u0005H\\u0086\\u0008J\\u0008\\u0010\\u0006\\u001a\\u00020\\u0005H\\u0001\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"getString\", \"\","
            + " \"getPublishedString\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "open class A {",
            "  inline fun getString(): String { return getPublishedString() }",
            "  @PublishedApi",
            "  internal fun getPublishedString(): String { return \"test\" }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  public final getString()Ljava/lang/String;",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "   L0",
            "    ICONST_0",
            "    ISTORE 1",
            "   L1",
            "    LINENUMBER 3 L1",
            "    ALOAD 0",
            "    INVOKEVIRTUAL com/example/buck/A.getPublishedString ()Ljava/lang/String;",
            "    ARETURN",
            "   L2",
            "    LOCALVARIABLE $i$f$getString I L1 L2 1",
            "    LOCALVARIABLE this Lcom/example/buck/A; L0 L2 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x11",
            "  public final getPublishedString()Ljava/lang/String;",
            "  @Lkotlin/PublishedApi;() // invisible",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "B.kt",
            "package com.example.buck",
            "class B {",
            "fun useInline(): String { return A().getString() }",
            "}")
        .testCanCompile();
  }

  @Test
  public void kotlinClassWithInlineMethodAndJvmName() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T94515328
      // Kotlin Metadata is different in Source and Jar -based modes (xi=48)
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0012\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0000\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u000e\\u0010\\u0004\\u001a\\u00020\\u0005H\\u0087\\u0008\\u00a2\\u0006\\u0002\\u0008\\u0006\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"getString\", \"\","
            + " \"someOtherName\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0012\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0000\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u000e\\u0010\\u0004\\u001a\\u00020\\u0005H\\u0087\\u0008\\u00a2\\u0006\\u0002\\u0008\\u0006\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"getString\", \"\","
            + " \"someOtherName\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "open class A {",
            "  @JvmName(\"someOtherName\")",
            "  inline fun getString(): String { return \"test\" }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  public final someOtherName()Ljava/lang/String;",
            "  @Lkotlin/jvm/JvmName;(name=\"someOtherName\") // invisible",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "   L0",
            "    ICONST_0",
            "    ISTORE 1",
            "   L1",
            "    LINENUMBER 4 L1",
            "    LDC \"test\"",
            "    ARETURN",
            "   L2",
            "    LOCALVARIABLE $i$f$someOtherName I L1 L2 1",
            "    LOCALVARIABLE this Lcom/example/buck/A; L0 L2 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 2",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "B.kt",
            "package com.example.buck",
            "class B {",
            "  fun useInline(): String { return A().getString() }",
            "}")
        .testCanCompile();
  }

  @Test
  public void kotlinClassWithInlineMethodUsingDefaultParam() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T94515328
      // Kotlin Metadata is different in Source and Jar -based modes (xi=48)
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0012\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0000\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0013\\u0010\\u0004\\u001a\\u00020\\u00052\\u0008\\u0008\\u0002\\u0010\\u0006\\u001a\\u00020\\u0005H\\u0086\\u0008\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"getString\", \"\","
            + " \"str\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0012\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0000\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0013\\u0010\\u0004\\u001a\\u00020\\u00052\\u0008\\u0008\\u0002\\u0010\\u0006\\u001a\\u00020\\u0005H\\u0086\\u0008\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"getString\", \"\","
            + " \"str\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "open class A {",
            "  inline fun getString(str: String = \"default\"): String { return str }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  public final getString(Ljava/lang/String;)Ljava/lang/String;",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "    // annotable parameter count: 1 (invisible)",
            "    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0",
            "   L0",
            "    ALOAD 1",
            "    LDC \"str\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "    ICONST_0",
            "    ISTORE 2",
            "   L1",
            "    LINENUMBER 3 L1",
            "    ALOAD 1",
            "    ARETURN",
            "   L2",
            "    LOCALVARIABLE $i$f$getString I L1 L2 2",
            "    LOCALVARIABLE this Lcom/example/buck/A; L0 L2 0",
            "    LOCALVARIABLE str Ljava/lang/String; L0 L2 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 3",
            "",
            "  // access flags 0x1009",
            "  public static synthetic"
                + " getString$default(Lcom/example/buck/A;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String;",
            "   L0",
            "    LINENUMBER 3 L0",
            "    ALOAD 3",
            "    IFNULL L1",
            "    NEW java/lang/UnsupportedOperationException",
            "    DUP",
            "    LDC \"Super calls with default arguments not supported in this target, function:"
                + " getString\"",
            "    INVOKESPECIAL java/lang/UnsupportedOperationException.<init>"
                + " (Ljava/lang/String;)V",
            "    ATHROW",
            "   L1",
            "    ILOAD 2",
            "    ICONST_1",
            "    IAND",
            "    IFEQ L2",
            "    LDC \"default\"",
            "    ASTORE 1",
            "   L2",
            "    ALOAD 1",
            "    LDC \"str\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "    ICONST_0",
            "    ISTORE 2",
            "   L3",
            "    LINENUMBER 3 L3",
            "    ALOAD 1",
            "    ARETURN",
            "   L4",
            "    LOCALVARIABLE $i$f$getString I L3 L4 2",
            "    LOCALVARIABLE $this Lcom/example/buck/A; L0 L4 0",
            "    LOCALVARIABLE str Ljava/lang/String; L0 L4 1",
            "    MAXSTACK = 3",
            "    MAXLOCALS = 4",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "B.kt",
            "package com.example.buck",
            "class B {",
            "fun useInline(): String { return A().getString() }",
            "}")
        .testCanCompile();
  }

  @Test
  public void kotlinInlineFunCreatingCapturingLambda() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T94515328
      // Kotlin Metadata is difference in Source and Jar -based modes (xi=48)
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 = "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=3, xi=176)";
    String metadata20 = "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=3, xi=176)";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "inline fun test(x: Int): () -> Unit = { println(x) }")
        .addExpectedStub("com/example/buck/AKt")
        .addExpectedStub(
            "com/example/buck/AKt$test$1",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "// signature" + " Ljava/lang/Object;Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;",
            "// declaration: com/example/buck/AKt$test$1"
                + " implements kotlin.jvm.functions.Function0<kotlin.Unit>",
            "public final class com/example/buck/AKt$test$1"
                + " implements kotlin/jvm/functions/Function0 {",
            "",
            "  // compiled from: A.kt",
            "  OUTERCLASS com/example/buck/AKt test (I)Lkotlin/jvm/functions/Function0;",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/AKt$test$1 null null",
            "",
            "  // access flags 0x1010",
            "  final synthetic I $x",
            "",
            "  // access flags 0x1",
            "  public <init>(I)V",
            "   L0",
            "    ALOAD 0",
            "    ILOAD 1",
            "    PUTFIELD com/example/buck/AKt$test$1.$x : I",
            "    ALOAD 0",
            "    INVOKESPECIAL java/lang/Object.<init> ()V",
            "    RETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L1 0",
            "    LOCALVARIABLE $x I L0 L1 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x11",
            "  public final invoke()V",
            "   L0",
            "    LINENUMBER 2 L0",
            "    ALOAD 0",
            "    GETFIELD com/example/buck/AKt$test$1.$x : I",
            "    ISTORE 1",
            "    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;",
            "    ILOAD 1",
            "    INVOKEVIRTUAL java/io/PrintStream.println (I)V",
            "   L1",
            "    LINENUMBER 2 L1",
            "    RETURN",
            "   L2",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L2 0",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge invoke()Ljava/lang/Object;",
            "   L0",
            "    LINENUMBER 2 L0",
            "    ALOAD 0",
            "    INVOKEVIRTUAL com/example/buck/AKt$test$1.invoke ()V",
            "    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L1 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 1",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void kotlinInlineFunCreatingSuspendLambda() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T94515328
      // Kotlin Metadata is difference in Source and Jar -based modes (xi=48)
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=3, xi=176, d1={\"\\u0000\\u000c\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0008\\u0010\\u0000\\u001a\\u00020\\u00012\\u0006\\u0010\\u0002\\u001a\\u00020\\u0003H\\n"
            + "\"}, d2={\"<anonymous>\", \"\", \"it\", \"\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=3, xi=176, d1={\"\\u0000\\u000c\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0008\\u0010\\u0000\\u001a\\u00020\\u00012\\u0006\\u0010\\u0002\\u001a\\u00020\\u0003H\\n"
            + "\"}, d2={\"<anonymous>\", \"\", \"it\", \"\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "inline fun test(): suspend (Int) -> Unit = { println(it) }")
        .addExpectedStub("com/example/buck/AKt")
        .addExpectedStub(
            "com/example/buck/AKt$test$1",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "// signature"
                + " Lkotlin/coroutines/jvm/internal/SuspendLambda;Lkotlin/jvm/functions/Function2<Ljava/lang/Integer;Lkotlin/coroutines/Continuation<-Lkotlin/Unit;>;Ljava/lang/Object;>;",
            "// declaration: com/example/buck/AKt$test$1 extends"
                + " kotlin.coroutines.jvm.internal.SuspendLambda implements"
                + " kotlin.jvm.functions.Function2<java.lang.Integer,"
                + " kotlin.coroutines.Continuation<? super kotlin.Unit>, java.lang.Object>",
            "public final class com/example/buck/AKt$test$1 extends"
                + " kotlin/coroutines/jvm/internal/SuspendLambda implements"
                + " kotlin/jvm/functions/Function2 {",
            "",
            "  // compiled from: A.kt",
            "  OUTERCLASS com/example/buck/AKt test ()Lkotlin/jvm/functions/Function2;",
            "",
            "  @Lkotlin/coroutines/jvm/internal/DebugMetadata;(f=\"A.kt\", l={}, i={}, s={}, n={},"
                + " m=\"invokeSuspend\", c=\"com.example.buck.AKt$test$1\")",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/AKt$test$1 null null",
            "",
            "  // access flags 0x0",
            "  I label",
            "",
            "  // access flags 0x1000",
            "  synthetic I I$0",
            "",
            "  // access flags 0x1",
            "  // signature (Lkotlin/coroutines/Continuation<-Lcom/example/buck/AKt$test$1;>;)V",
            "  // declaration: void <init>(kotlin.coroutines.Continuation<? super"
                + " com.example.buck.AKt$test$1>)",
            "  public <init>(Lkotlin/coroutines/Continuation;)V",
            "   L0",
            "    ALOAD 0",
            "    ICONST_2",
            "    ALOAD 1",
            "    INVOKESPECIAL kotlin/coroutines/jvm/internal/SuspendLambda.<init>"
                + " (ILkotlin/coroutines/Continuation;)V",
            "    RETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L1 0",
            "    LOCALVARIABLE $completion Lkotlin/coroutines/Continuation; L0 L1 1",
            "    MAXSTACK = 3",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x11",
            "  public final invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;",
            "   L0",
            "    INVOKESTATIC kotlin/coroutines/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED"
                + " ()Ljava/lang/Object;",
            "   L1",
            "    LINENUMBER 2 L1",
            "    POP",
            "    ALOAD 0",
            "    GETFIELD com/example/buck/AKt$test$1.label : I",
            "    TABLESWITCH",
            "      0: L2",
            "      default: L3",
            "   L2",
            "    ALOAD 1",
            "    INVOKESTATIC kotlin/ResultKt.throwOnFailure (Ljava/lang/Object;)V",
            "   L4",
            "    ALOAD 0",
            "    GETFIELD com/example/buck/AKt$test$1.I$0 : I",
            "    ISTORE 2",
            "   L5",
            "    LINENUMBER 2 L5",
            "    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;",
            "    ILOAD 2",
            "    INVOKEVIRTUAL java/io/PrintStream.println (I)V",
            "   L6",
            "    LINENUMBER 2 L6",
            "    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;",
            "    ARETURN",
            "   L3",
            "    LINENUMBER 2 L3",
            "    NEW java/lang/IllegalStateException",
            "    DUP",
            "    LDC \"call to 'resume' before 'invoke' with coroutine\"",
            "    INVOKESPECIAL java/lang/IllegalStateException.<init> (Ljava/lang/String;)V",
            "    ATHROW",
            "   L7",
            "    LOCALVARIABLE it I L5 L3 2",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L7 0",
            "    LOCALVARIABLE $result Ljava/lang/Object; L4 L3 1",
            "    MAXSTACK = 3",
            "    MAXLOCALS = 3",
            "",
            "  // access flags 0x11",
            "  // signature"
                + " (Ljava/lang/Object;Lkotlin/coroutines/Continuation<*>;)Lkotlin/coroutines/Continuation<Lkotlin/Unit;>;",
            "  // declaration: kotlin.coroutines.Continuation<kotlin.Unit> create(java.lang.Object,"
                + " kotlin.coroutines.Continuation<?>)",
            "  public final"
                + " create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;",
            "   L0",
            "    NEW com/example/buck/AKt$test$1",
            "    DUP",
            "    ALOAD 2",
            "    INVOKESPECIAL com/example/buck/AKt$test$1.<init>"
                + " (Lkotlin/coroutines/Continuation;)V",
            "    ASTORE 3",
            "    ALOAD 3",
            "    ALOAD 1",
            "    CHECKCAST java/lang/Number",
            "    INVOKEVIRTUAL java/lang/Number.intValue ()I",
            "    PUTFIELD com/example/buck/AKt$test$1.I$0 : I",
            "    ALOAD 3",
            "    CHECKCAST kotlin/coroutines/Continuation",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L1 0",
            "    LOCALVARIABLE value Ljava/lang/Object; L0 L1 1",
            "    LOCALVARIABLE $completion Lkotlin/coroutines/Continuation; L0 L1 2",
            "    MAXSTACK = 3",
            "    MAXLOCALS = 4",
            "",
            "  // access flags 0x11",
            "  // signature (ILkotlin/coroutines/Continuation<-Lkotlin/Unit;>;)Ljava/lang/Object;",
            "  // declaration:  invoke(int, kotlin.coroutines.Continuation<? super kotlin.Unit>)",
            "  public final invoke(ILkotlin/coroutines/Continuation;)Ljava/lang/Object;",
            "   L0",
            "    ALOAD 0",
            "    ILOAD 1",
            "    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;",
            "    ALOAD 2",
            "    INVOKEVIRTUAL com/example/buck/AKt$test$1.create"
                + " (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;",
            "    CHECKCAST com/example/buck/AKt$test$1",
            "    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;",
            "    INVOKEVIRTUAL com/example/buck/AKt$test$1.invokeSuspend"
                + " (Ljava/lang/Object;)Ljava/lang/Object;",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L1 0",
            "    LOCALVARIABLE p1 I L0 L1 1",
            "    LOCALVARIABLE p2 Lkotlin/coroutines/Continuation; L0 L1 2",
            "    MAXSTACK = 3",
            "    MAXLOCALS = 3",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge"
                + " invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
            "   L0",
            "    ALOAD 0",
            "    ALOAD 1",
            "    CHECKCAST java/lang/Number",
            "    INVOKEVIRTUAL java/lang/Number.intValue ()I",
            "    ALOAD 2",
            "    CHECKCAST kotlin/coroutines/Continuation",
            "    INVOKEVIRTUAL com/example/buck/AKt$test$1.invoke"
                + " (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L1 0",
            "    LOCALVARIABLE p1 Ljava/lang/Object; L0 L1 1",
            "    LOCALVARIABLE p2 Ljava/lang/Object; L0 L1 2",
            "    MAXSTACK = 3",
            "    MAXLOCALS = 3",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void kotlinInlineFunCreatingNestedLambdas() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T94515328
      // Kotlin Metadata is different in Source and Jar -based modes (xi=48)
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 = "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=3, xi=176)";
    String metadata20 = "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=3, xi=176)";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "inline fun test(x: Int): (Int) -> (Int) -> Int = { y -> { z -> x + y + z } }")
        .addExpectedStub("com/example/buck/AKt")
        .addExpectedStub(
            "com/example/buck/AKt$test$1",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "// signature"
                + " Ljava/lang/Object;Lkotlin/jvm/functions/Function1<Ljava/lang/Integer;Lkotlin/jvm/functions/Function1<-Ljava/lang/Integer;+Ljava/lang/Integer;>;>;",
            "// declaration: com/example/buck/AKt$test$1"
                + " implements kotlin.jvm.functions.Function1<java.lang.Integer,"
                + " kotlin.jvm.functions.Function1<? super java.lang.Integer, ? extends"
                + " java.lang.Integer>>",
            "public final class com/example/buck/AKt$test$1"
                + " implements kotlin/jvm/functions/Function1 {",
            "",
            "  // compiled from: A.kt",
            "  OUTERCLASS com/example/buck/AKt test (I)Lkotlin/jvm/functions/Function1;",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/AKt$test$1 null null",
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/AKt$test$1$1 null null",
            "",
            "  // access flags 0x1010",
            "  final synthetic I $x",
            "",
            "  // access flags 0x1",
            "  public <init>(I)V",
            "   L0",
            "    ALOAD 0",
            "    ILOAD 1",
            "    PUTFIELD com/example/buck/AKt$test$1.$x : I",
            "    ALOAD 0",
            "    INVOKESPECIAL java/lang/Object.<init> ()V",
            "    RETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L1 0",
            "    LOCALVARIABLE $x I L0 L1 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x11",
            "  // signature"
                + " (I)Lkotlin/jvm/functions/Function1<Ljava/lang/Integer;Ljava/lang/Integer;>;",
            "  // declaration: kotlin.jvm.functions.Function1<java.lang.Integer, java.lang.Integer>"
                + " invoke(int)",
            "  public final invoke(I)Lkotlin/jvm/functions/Function1;",
            "   L0",
            "    LINENUMBER 2 L0",
            "    NEW com/example/buck/AKt$test$1$1",
            "    DUP",
            "    ALOAD 0",
            "    GETFIELD com/example/buck/AKt$test$1.$x : I",
            "    ILOAD 1",
            "    INVOKESPECIAL com/example/buck/AKt$test$1$1.<init> (II)V",
            "    CHECKCAST kotlin/jvm/functions/Function1",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L1 0",
            "    LOCALVARIABLE y I L0 L1 1",
            "    MAXSTACK = 4",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge invoke(Ljava/lang/Object;)Ljava/lang/Object;",
            "   L0",
            "    LINENUMBER 2 L0",
            "    ALOAD 0",
            "    ALOAD 1",
            "    CHECKCAST java/lang/Number",
            "    INVOKEVIRTUAL java/lang/Number.intValue ()I",
            "    INVOKEVIRTUAL com/example/buck/AKt$test$1.invoke"
                + " (I)Lkotlin/jvm/functions/Function1;",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1; L0 L1 0",
            "    LOCALVARIABLE p1 Ljava/lang/Object; L0 L1 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "}")
        .addExpectedStub(
            "com/example/buck/AKt$test$1$1",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "// signature"
                + " Ljava/lang/Object;Lkotlin/jvm/functions/Function1<Ljava/lang/Integer;Ljava/lang/Integer;>;",
            "// declaration: com/example/buck/AKt$test$1$1"
                + " implements kotlin.jvm.functions.Function1<java.lang.Integer,"
                + " java.lang.Integer>",
            "public final class com/example/buck/AKt$test$1$1"
                + " implements kotlin/jvm/functions/Function1 {",
            "",
            "  // compiled from: A.kt",
            "  OUTERCLASS com/example/buck/AKt$test$1 invoke (I)Lkotlin/jvm/functions/Function1;",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/AKt$test$1 null null",
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/AKt$test$1$1 null null",
            "",
            "  // access flags 0x1010",
            "  final synthetic I $x",
            "",
            "  // access flags 0x1010",
            "  final synthetic I $y",
            "",
            "  // access flags 0x1",
            "  public <init>(II)V",
            "   L0",
            "    ALOAD 0",
            "    ILOAD 1",
            "    PUTFIELD com/example/buck/AKt$test$1$1.$x : I",
            "    ALOAD 0",
            "    ILOAD 2",
            "    PUTFIELD com/example/buck/AKt$test$1$1.$y : I",
            "    ALOAD 0",
            "    INVOKESPECIAL java/lang/Object.<init> ()V",
            "    RETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1$1; L0 L1 0",
            "    LOCALVARIABLE $x I L0 L1 1",
            "    LOCALVARIABLE $y I L0 L1 2",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 3",
            "",
            "  // access flags 0x11",
            "  public final invoke(I)Ljava/lang/Integer;",
            "   L0",
            "    LINENUMBER 2 L0",
            "    ALOAD 0",
            "    GETFIELD com/example/buck/AKt$test$1$1.$x : I",
            "    ALOAD 0",
            "    GETFIELD com/example/buck/AKt$test$1$1.$y : I",
            "    IADD",
            "    ILOAD 1",
            "    IADD",
            "    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1$1; L0 L1 0",
            "    LOCALVARIABLE z I L0 L1 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge invoke(Ljava/lang/Object;)Ljava/lang/Object;",
            "   L0",
            "    LINENUMBER 2 L0",
            "    ALOAD 0",
            "    ALOAD 1",
            "    CHECKCAST java/lang/Number",
            "    INVOKEVIRTUAL java/lang/Number.intValue ()I",
            "    INVOKEVIRTUAL com/example/buck/AKt$test$1$1.invoke (I)Ljava/lang/Integer;",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/AKt$test$1$1; L0 L1 0",
            "    LOCALVARIABLE p1 Ljava/lang/Object; L0 L1 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void kotlinClassNoInlineMethod() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T94515328
      // Kotlin Metadata is different in Source and Jar -based modes (xi=48)
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0016\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0006\\u0010\\u0004\\u001a\\u00020\\u0005J\\u0006\\u0010\\u0006\\u001a\\u00020\\u0007\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"getString\", \"\","
            + " \"someOtherMethod\", \"\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0016\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0006\\u0010\\u0004\\u001a\\u00020\\u0005J\\u0006\\u0010\\u0006\\u001a\\u00020\\u0007\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"getString\", \"\","
            + " \"someOtherMethod\", \"\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "open class A {",
            "  fun getString(): String { return \"test\" }",
            "  fun someOtherMethod() {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  public final getString()Ljava/lang/String;",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "",
            "  // access flags 0x11",
            "  public final someOtherMethod()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void kotlinClassWithInlineProperty() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T94515328
      // Kotlin Metadata is different in Source and Jar -based modes (xi=48)
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0014\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000b\\n"
            + "\\u0002\\u0008\\u0007\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003R&\\u0010\\u0006\\u001a\\u00020\\u00052\\u0006\\u0010\\u0004\\u001a\\u00020\\u00058\\u00c6\\u0002@\\u00c6\\u0002X\\u0086\\u000e\\u00a2\\u0006\\u000c\\u001a\\u0004\\u0008\\u0007\\u0010\\u0008\\\"\\u0004\\u0008\\u0009\\u0010\\n"
            + "R&\\u0010\\u000b\\u001a\\u00020\\u00052\\u0006\\u0010\\u0004\\u001a\\u00020\\u00058\\u00c6\\u0002@\\u00c6\\u0002X\\u0086\\u000e\\u00a2\\u0006\\u000c\\u001a\\u0004\\u0008\\u000b\\u0010\\u0008\\\"\\u0004\\u0008\\u000c\\u0010\\n"
            + "\"}, d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"value\", \"\","
            + " \"someProperty\", \"getSomeProperty\", \"()Z\", \"setSomeProperty\", \"(Z)V\","
            + " \"isAProperty\", \"setAProperty\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0014\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000b\\n"
            + "\\u0002\\u0008\\u0007\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003R&\\u0010\\u0006\\u001a\\u00020\\u00052\\u0006\\u0010\\u0004\\u001a\\u00020\\u00058\\u00c6\\u0002@\\u00c6\\u0002X\\u0086\\u000e\\u00a2\\u0006\\u000c\\u001a\\u0004\\u0008\\u0007\\u0010\\u0008\\\"\\u0004\\u0008\\u0009\\u0010\\n"
            + "R&\\u0010\\u000b\\u001a\\u00020\\u00052\\u0006\\u0010\\u0004\\u001a\\u00020\\u00058\\u00c6\\u0002@\\u00c6\\u0002X\\u0086\\u000e\\u00a2\\u0006\\u000c\\u001a\\u0004\\u0008\\u000b\\u0010\\u0008\\\"\\u0004\\u0008\\u000c\\u0010\\n"
            + "\"}, d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"value\", \"\","
            + " \"someProperty\", \"getSomeProperty\", \"()Z\", \"setSomeProperty\", \"(Z)V\","
            + " \"isAProperty\", \"setAProperty\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "open class A {",
            "  inline var someProperty: Boolean",
            "    get() = true",
            "    set(value) {}",
            "  inline var isAProperty: Boolean",
            "    get() = true",
            "    set(value) {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  public final getSomeProperty()Z",
            "   L0",
            "    ICONST_0",
            "    ISTORE 1",
            "   L1",
            "    LINENUMBER 4 L1",
            "    ICONST_1",
            "    IRETURN",
            "   L2",
            "    LOCALVARIABLE $i$f$getSomeProperty I L1 L2 1",
            "    LOCALVARIABLE this Lcom/example/buck/A; L0 L2 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x11",
            "  public final setSomeProperty(Z)V",
            "   L0",
            "    ICONST_0",
            "    ISTORE 2",
            "   L1",
            "    LINENUMBER 5 L1",
            "    RETURN",
            "   L2",
            "    LOCALVARIABLE $i$f$setSomeProperty I L1 L2 2",
            "    LOCALVARIABLE this Lcom/example/buck/A; L0 L2 0",
            "    LOCALVARIABLE value Z L0 L2 1",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 3",
            "",
            "  // access flags 0x11",
            "  public final isAProperty()Z",
            "   L0",
            "    ICONST_0",
            "    ISTORE 1",
            "   L1",
            "    LINENUMBER 7 L1",
            "    ICONST_1",
            "    IRETURN",
            "   L2",
            "    LOCALVARIABLE $i$f$isAProperty I L1 L2 1",
            "    LOCALVARIABLE this Lcom/example/buck/A; L0 L2 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x11",
            "  public final setAProperty(Z)V",
            "   L0",
            "    ICONST_0",
            "    ISTORE 2",
            "   L1",
            "    LINENUMBER 8 L1",
            "    RETURN",
            "   L2",
            "    LOCALVARIABLE $i$f$setAProperty I L1 L2 2",
            "    LOCALVARIABLE this Lcom/example/buck/A; L0 L2 0",
            "    LOCALVARIABLE value Z L0 L2 1",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 3",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "B.kt",
            "package com.example.buck",
            "class B {",
            "  fun useInlineProperties() {",
            "    A().someProperty",
            "    A().someProperty = true",
            "    A().isAProperty",
            "    A().isAProperty = true",
            "  }",
            "}")
        .testCanCompile();
  }

  @Test
  public void kotlinClassWithInlineExtensionMethodUsingLambda() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T53836707 the methods in the synthetic class are wrongly stripped, preventing
      //  compilation
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=2, xi=48, d1={\"\\u0000\\n"
            + "\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0002\\u0018\\u0002\\u001a\\r"
            + "\\u0010\\u0000\\u001a\\u00020\\u0001*\\u00020\\u0002H\\u0086\\u0008\"},"
            + " d2={\"getString\", \"\", \"Lcom/example/buck/A;\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=2, xi=48, d1={\"\\u0000\\n"
            + "\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0002\\u0018\\u0002\\u001a\\r"
            + "\\u0010\\u0000\\u001a\\u00020\\u0001*\\u00020\\u0002H\\u0086\\u0008\"},"
            + " d2={\"getString\", \"\", \"Lcom/example/buck/A;\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "open class A {",
            "  fun post(function: (String) -> String): String {",
            "    return function(\"test\")",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "B.kt",
            "package com.example.buck",
            "inline fun A.getString(): String { ",
            "  return post { x -> x }",
            "}")
        .addExpectedStub(
            "com/example/buck/BKt",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "public final class com/example/buck/BKt {",
            "",
            "  // compiled from: B.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x19",
            "  public final static getString(Lcom/example/buck/A;)Ljava/lang/String;",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "    // annotable parameter count: 1 (invisible)",
            "    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0",
            "   L0",
            "    ALOAD 0",
            "    LDC \"<this>\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "    ICONST_0",
            "    ISTORE 1",
            "   L1",
            "    LINENUMBER 3 L1",
            "    ALOAD 0",
            "    GETSTATIC com/example/buck/BKt$getString$1.INSTANCE :"
                + " Lcom/example/buck/BKt$getString$1;",
            "    CHECKCAST kotlin/jvm/functions/Function1",
            "    INVOKEVIRTUAL com/example/buck/A.post"
                + " (Lkotlin/jvm/functions/Function1;)Ljava/lang/String;",
            "    ARETURN",
            "   L2",
            "    LOCALVARIABLE $i$f$getString I L1 L2 1",
            "    LOCALVARIABLE $this$getString Lcom/example/buck/A; L0 L2 0",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x2",
            "  private <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/BKt$getString$1",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "// signature"
                + " Ljava/lang/Object;Lkotlin/jvm/functions/Function1<Ljava/lang/String;Ljava/lang/String;>;",
            "// declaration: com/example/buck/BKt$getString$1"
                + " implements kotlin.jvm.functions.Function1<java.lang.String, java.lang.String>",
            "public final class com/example/buck/BKt$getString$1"
                + " implements kotlin/jvm/functions/Function1 {",
            "",
            "  // compiled from: B.kt",
            "  OUTERCLASS com/example/buck/BKt getString (Lcom/example/buck/A;)Ljava/lang/String;",
            "",
            isKotlin21()
                ? "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=3, xi=176)"
                : "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=3, xi=176)",
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/BKt$getString$1 null null",
            "",
            "  // access flags 0x19",
            "  public final static Lcom/example/buck/BKt$getString$1; INSTANCE",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "   L0",
            "    ALOAD 0",
            "    INVOKESPECIAL java/lang/Object.<init> ()V",
            "    RETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/BKt$getString$1; L0 L1 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 1",
            "",
            "  // access flags 0x11",
            "  public final invoke(Ljava/lang/String;)Ljava/lang/String;",
            "   L0",
            "    ALOAD 1",
            "    LDC \"x\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "   L1",
            "    LINENUMBER 3 L1",
            "    ALOAD 1",
            "    ARETURN",
            "   L2",
            "    LOCALVARIABLE this Lcom/example/buck/BKt$getString$1; L0 L2 0",
            "    LOCALVARIABLE x Ljava/lang/String; L0 L2 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge invoke(Ljava/lang/Object;)Ljava/lang/Object;",
            "   L0",
            "    LINENUMBER 3 L0",
            "    ALOAD 0",
            "    ALOAD 1",
            "    CHECKCAST java/lang/String",
            "    INVOKEVIRTUAL com/example/buck/BKt$getString$1.invoke"
                + " (Ljava/lang/String;)Ljava/lang/String;",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/BKt$getString$1; L0 L1 0",
            "    LOCALVARIABLE p1 Ljava/lang/Object; L0 L1 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x8",
            "  static <clinit>()V",
            "    NEW com/example/buck/BKt$getString$1",
            "    DUP",
            "    INVOKESPECIAL com/example/buck/BKt$getString$1.<init> ()V",
            "    PUTSTATIC com/example/buck/BKt$getString$1.INSTANCE :"
                + " Lcom/example/buck/BKt$getString$1;",
            "    RETURN",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 0",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "C.kt",
            "package com.example.buck",
            "class C: A() {",
            "fun useInlineExtension(): String { return getString() }",
            "}")
        .testCanCompile();
  }

  @Test
  public void kotlinClassWithInlineExtensionMethodUsingLambdaAndDefaultMethod() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T53836707 the methods in the synthetic class are wrongly stripped, preventing
      //  compilation
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u001e\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u0002\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0002\\u0018\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J'\\u0010\\u0004\\u001a\\u00020\\u00052\\u0019\\u0008\\u0002\\u0010\\u0006\\u001a\\u0013\\u0012\\u0004\\u0012\\u00020\\u0008\\u0012\\u0004\\u0012\\u00020\\u00050\\u0007\\u00a2\\u0006\\u0002\\u0008\\u0009H\\u0086\\u0008\\u00f8\\u0001\\u0000\\u0082\\u0002\\u0007\\n"
            + "\\u0005\\u0008\\u009920\\u0001\"}, d2={\"Lcom/example/buck/A;\", \"\", \"<init>\","
            + " \"()V\", \"someMethod\", \"\", \"function\", \"Lkotlin/Function1;\", \"\","
            + " \"Lkotlin/ExtensionFunctionType;\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u001e\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u0002\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0002\\u0018\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J'\\u0010\\u0004\\u001a\\u00020\\u00052\\u0019\\u0008\\u0002\\u0010\\u0006\\u001a\\u0013\\u0012\\u0004\\u0012\\u00020\\u0008\\u0012\\u0004\\u0012\\u00020\\u00050\\u0007\\u00a2\\u0006\\u0002\\u0008\\u0009H\\u0086\\u0008\\u00f8\\u0001\\u0000\\u0082\\u0002\\u0007\\n"
            + "\\u0005\\u0008\\u009920\\u0001\"}, d2={\"Lcom/example/buck/A;\", \"\", \"<init>\","
            + " \"()V\", \"someMethod\", \"\", \"function\", \"Lkotlin/Function1;\", \"\","
            + " \"Lkotlin/ExtensionFunctionType;\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "open class A {",
            "  inline fun someMethod(function: String.() -> Unit = {}): Unit {",
            "    function(\"test\")",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  // signature (Lkotlin/jvm/functions/Function1<-Ljava/lang/String;Lkotlin/Unit;>;)V",
            "  // declaration: void someMethod(kotlin.jvm.functions.Function1<? super"
                + " java.lang.String, kotlin.Unit>)",
            "  public final someMethod(Lkotlin/jvm/functions/Function1;)V",
            "    // annotable parameter count: 1 (invisible)",
            "    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0",
            "   L0",
            "    ALOAD 1",
            "    LDC \"function\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "    ICONST_0",
            "    ISTORE 2",
            "   L1",
            "    LINENUMBER 4 L1",
            "    ALOAD 1",
            "    LDC \"test\"",
            "    INVOKEINTERFACE kotlin/jvm/functions/Function1.invoke"
                + " (Ljava/lang/Object;)Ljava/lang/Object; (itf)",
            "    POP",
            "   L2",
            "    LINENUMBER 5 L2",
            "    RETURN",
            "   L3",
            "    LOCALVARIABLE $i$f$someMethod I L1 L3 2",
            "    LOCALVARIABLE this Lcom/example/buck/A; L0 L3 0",
            "    LOCALVARIABLE function Lkotlin/jvm/functions/Function1; L0 L3 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 3",
            "",
            "  // access flags 0x1009",
            "  public static synthetic"
                + " someMethod$default(Lcom/example/buck/A;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V",
            "   L0",
            "    LINENUMBER 3 L0",
            "    ALOAD 3",
            "    IFNULL L1",
            "    NEW java/lang/UnsupportedOperationException",
            "    DUP",
            "    LDC \"Super calls with default arguments not supported in this target, function:"
                + " someMethod\"",
            "    INVOKESPECIAL java/lang/UnsupportedOperationException.<init>"
                + " (Ljava/lang/String;)V",
            "    ATHROW",
            "   L1",
            "    ILOAD 2",
            "    ICONST_1",
            "    IAND",
            "    IFEQ L2",
            "    GETSTATIC com/example/buck/A$someMethod$1.INSTANCE :"
                + " Lcom/example/buck/A$someMethod$1;",
            "    CHECKCAST kotlin/jvm/functions/Function1",
            "    ASTORE 1",
            "   L2",
            "    ALOAD 1",
            "    LDC \"function\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "    ICONST_0",
            "    ISTORE 2",
            "   L3",
            "    LINENUMBER 4 L3",
            "    ALOAD 1",
            "    LDC \"test\"",
            "    INVOKEINTERFACE kotlin/jvm/functions/Function1.invoke"
                + " (Ljava/lang/Object;)Ljava/lang/Object; (itf)",
            "    POP",
            "   L4",
            "    LINENUMBER 5 L4",
            "    RETURN",
            "   L5",
            "    LOCALVARIABLE $i$f$someMethod I L3 L5 2",
            "    LOCALVARIABLE $this Lcom/example/buck/A; L0 L5 0",
            "    LOCALVARIABLE function Lkotlin/jvm/functions/Function1; L0 L5 1",
            "    MAXSTACK = 3",
            "    MAXLOCALS = 4",
            "}")
        .addExpectedStub(
            "com/example/buck/A$someMethod$1",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "// signature"
                + " Ljava/lang/Object;Lkotlin/jvm/functions/Function1<Ljava/lang/String;Lkotlin/Unit;>;",
            "// declaration: com/example/buck/A$someMethod$1"
                + " implements kotlin.jvm.functions.Function1<java.lang.String, kotlin.Unit>",
            "public final class com/example/buck/A$someMethod$1"
                + " implements kotlin/jvm/functions/Function1 {",
            "",
            "  // compiled from: A.kt",
            "  OUTERCLASS com/example/buck/A someMethod$default"
                + " (Lcom/example/buck/A;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V",
            "",
            isKotlin21()
                ? "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=3, xi=176)"
                : "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=3, xi=176)",
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/A$someMethod$1 null null",
            "",
            "  // access flags 0x19",
            "  public final static Lcom/example/buck/A$someMethod$1; INSTANCE",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "   L0",
            "    ALOAD 0",
            "    INVOKESPECIAL java/lang/Object.<init> ()V",
            "    RETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/A$someMethod$1; L0 L1 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 1",
            "",
            "  // access flags 0x11",
            "  public final invoke(Ljava/lang/String;)V",
            "   L0",
            "    ALOAD 1",
            "    LDC \"<this>\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "   L1",
            "    LINENUMBER 3 L1",
            "    RETURN",
            "   L2",
            "    LOCALVARIABLE this Lcom/example/buck/A$someMethod$1; L0 L2 0",
            "    LOCALVARIABLE <this> Ljava/lang/String; L0 L2 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge invoke(Ljava/lang/Object;)Ljava/lang/Object;",
            "   L0",
            "    LINENUMBER 3 L0",
            "    ALOAD 0",
            "    ALOAD 1",
            "    CHECKCAST java/lang/String",
            "    INVOKEVIRTUAL com/example/buck/A$someMethod$1.invoke (Ljava/lang/String;)V",
            "    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/A$someMethod$1; L0 L1 0",
            "    LOCALVARIABLE p1 Ljava/lang/Object; L0 L1 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x8",
            "  static <clinit>()V",
            "    NEW com/example/buck/A$someMethod$1",
            "    DUP",
            "    INVOKESPECIAL com/example/buck/A$someMethod$1.<init> ()V",
            "    PUTSTATIC com/example/buck/A$someMethod$1.INSTANCE :"
                + " Lcom/example/buck/A$someMethod$1;",
            "    RETURN",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 0",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "B.kt",
            "package com.example.buck",
            "class B: A() {",
            "fun useInlineExtension(): Unit { someMethod() }",
            "}")
        .testCanCompile();
  }

  @Test
  public void kotlinClassWithInlineExtensionMethodThatImplementsInterface() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T53836707 the methods in the synthetic class are wrongly stripped, preventing
      //  compilation
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=2, xi=48, d1={\"\\u0000\\n"
            + "\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002\\n"
            + "\\u0002\\u0018\\u0002\\u001a\\r"
            + "\\u0010\\u0000\\u001a\\u00020\\u0001*\\u00020\\u0002H\\u0086\\u0008\"},"
            + " d2={\"useSomeInterface\", \"\", \"Lcom/example/buck/A;\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=2, xi=48, d1={\"\\u0000\\n"
            + "\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002\\n"
            + "\\u0002\\u0018\\u0002\\u001a\\r"
            + "\\u0010\\u0000\\u001a\\u00020\\u0001*\\u00020\\u0002H\\u0086\\u0008\"},"
            + " d2={\"useSomeInterface\", \"\", \"Lcom/example/buck/A;\"})";
    String innerMetadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=176, d1={\"\\u0000\\u000f\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002*\\u0001\\u0000\\u0008\\n"
            + "\\u0018\\u00002\\u00020\\u0001J\\u0008\\u0010\\u0002\\u001a\\u00020\\u0003H\\u0016\"},"
            + " d2={\"com/example/buck/BKt$useSomeInterface$1\","
            + " \"Lcom/example/buck/A$SomeInterface;\", \"someMethod\", \"\"})";
    String innerMetadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=176, d1={\"\\u0000\\u000f\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002*\\u0001\\u0000\\u0008\\n"
            + "\\u0018\\u00002\\u00020\\u0001J\\u0008\\u0010\\u0002\\u001a\\u00020\\u0003H\\u0016\"},"
            + " d2={\"com/example/buck/BKt$useSomeInterface$1\","
            + " \"Lcom/example/buck/A$SomeInterface;\", \"someMethod\", \"\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "open class A {",
            "  interface SomeInterface {",
            "    fun someMethod()",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "B.kt",
            "package com.example.buck",
            "inline fun A.useSomeInterface() {",
            "  object : A.SomeInterface {",
            "    override fun someMethod() {",
            "      // something here",
            "    }",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/BKt",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "public final class com/example/buck/BKt {",
            "",
            "  // compiled from: B.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x19",
            "  public final static useSomeInterface(Lcom/example/buck/A;)V",
            "    // annotable parameter count: 1 (invisible)",
            "    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0",
            "   L0",
            "    ALOAD 0",
            "    LDC \"<this>\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "    ICONST_0",
            "    ISTORE 1",
            "   L1",
            "    LINENUMBER 3 L1",
            "    NEW com/example/buck/BKt$useSomeInterface$1",
            "    DUP",
            "    INVOKESPECIAL com/example/buck/BKt$useSomeInterface$1.<init> ()V",
            "    POP",
            "   L2",
            "    LINENUMBER 8 L2",
            "    RETURN",
            "   L3",
            "    LOCALVARIABLE $i$f$useSomeInterface I L1 L3 1",
            "    LOCALVARIABLE $this$useSomeInterface Lcom/example/buck/A; L0 L3 0",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x2",
            "  private <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/BKt$useSomeInterface$1",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "public final class com/example/buck/BKt$useSomeInterface$1 implements"
                + " com/example/buck/A$SomeInterface {",
            "",
            "  // compiled from: B.kt",
            "  OUTERCLASS com/example/buck/BKt useSomeInterface (Lcom/example/buck/A;)V",
            "",
            isKotlin21() ? innerMetadata21 : innerMetadata20,
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/BKt$useSomeInterface$1 null null",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "   L0",
            "    LINENUMBER 3 L0",
            "    ALOAD 0",
            "    INVOKESPECIAL java/lang/Object.<init> ()V",
            "    RETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/BKt$useSomeInterface$1; L0 L1 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 1",
            "",
            "  // access flags 0x1",
            "  public someMethod()V",
            "   L0",
            "    LINENUMBER 6 L0",
            "    RETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/BKt$useSomeInterface$1; L0 L1 0",
            "    MAXSTACK = 0",
            "    MAXLOCALS = 1",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "C.kt",
            "package com.example.buck",
            "class C: A() {",
            "fun useInlineExtension() { useSomeInterface() }",
            "}")
        .testCanCompile();
  }

  @Test
  public void kotlinClassWithInlineExtensionMethodFromInnerClassThatImplementsInterface()
      throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T53836707 the methods in the synthetic class are wrongly stripped, preventing
      //  compilation
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u000c\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001:\\u0001\\u0004B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003\"},"
            + " d2={\"Lcom/example/buck/B;\", \"\", \"<init>\", \"()V\", \"C\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u000c\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001:\\u0001\\u0004B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003\"},"
            + " d2={\"Lcom/example/buck/B;\", \"\", \"<init>\", \"()V\", \"C\"})";
    String innerMetadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0010\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0009\\u0010\\u0004\\u001a\\u00020\\u0005H\\u0086\\u0008\"},"
            + " d2={\"Lcom/example/buck/B$C;\", \"\", \"<init>\", \"()V\", \"useSomeInterface\","
            + " \"\"})";
    String innerMetadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0010\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0009\\u0010\\u0004\\u001a\\u00020\\u0005H\\u0086\\u0008\"},"
            + " d2={\"Lcom/example/buck/B$C;\", \"\", \"<init>\", \"()V\", \"useSomeInterface\","
            + " \"\"})";
    String innerInnerMetadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=176, d1={\"\\u0000\\u000f\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0018\\u0002*\\u0001\\u0000\\u0008\\n"
            + "\\u0018\\u00002\\u00020\\u0001J\\u0008\\u0010\\u0002\\u001a\\u00020\\u0003H\\u0016\"},"
            + " d2={\"com/example/buck/B$C$useSomeInterface$1\","
            + " \"Lcom/example/buck/A$SomeInterface;\", \"someMethod\", \"Lcom/example/buck/A;\"})";
    String innerInnerMetadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=176, d1={\"\\u0000\\u000f\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0018\\u0002*\\u0001\\u0000\\u0008\\n"
            + "\\u0018\\u00002\\u00020\\u0001J\\u0008\\u0010\\u0002\\u001a\\u00020\\u0003H\\u0016\"},"
            + " d2={\"com/example/buck/B$C$useSomeInterface$1\","
            + " \"Lcom/example/buck/A$SomeInterface;\", \"someMethod\", \"Lcom/example/buck/A;\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "open class A {",
            "  interface SomeInterface {",
            "    fun someMethod(): A",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "B.kt",
            "package com.example.buck",
            "open class B {",
            "  open class C {",
            "    inline fun useSomeInterface() {",
            "      val a = A()",
            "      object : A.SomeInterface {",
            "        override fun someMethod() = a",
            "      }",
            "    }",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/B",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/B {",
            "",
            "  // compiled from: B.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/B$C",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/B$C {",
            "",
            "  // compiled from: B.kt",
            "",
            isKotlin21() ? innerMetadata21 : innerMetadata20,
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  public final useSomeInterface()V",
            "   L0",
            "    ICONST_0",
            "    ISTORE 1",
            "   L1",
            "    LINENUMBER 5 L1",
            "    NEW com/example/buck/A",
            "    DUP",
            "    INVOKESPECIAL com/example/buck/A.<init> ()V",
            "    ASTORE 2",
            "   L2",
            "    LINENUMBER 6 L2",
            "    NEW com/example/buck/B$C$useSomeInterface$1",
            "    DUP",
            "    ALOAD 2",
            "    INVOKESPECIAL com/example/buck/B$C$useSomeInterface$1.<init>"
                + " (Lcom/example/buck/A;)V",
            "    POP",
            "   L3",
            "    LINENUMBER 9 L3",
            "    RETURN",
            "   L4",
            "    LOCALVARIABLE $i$f$useSomeInterface I L1 L4 1",
            "    LOCALVARIABLE a Lcom/example/buck/A; L2 L4 2",
            "    LOCALVARIABLE this Lcom/example/buck/B$C; L0 L4 0",
            "    MAXSTACK = 3",
            "    MAXLOCALS = 3",
            "}")
        .addExpectedStub(
            "com/example/buck/B$C$useSomeInterface$1",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "public final class com/example/buck/B$C$useSomeInterface$1 implements"
                + " com/example/buck/A$SomeInterface {",
            "",
            "  // compiled from: B.kt",
            "  OUTERCLASS com/example/buck/B$C useSomeInterface ()V",
            "",
            isKotlin21() ? innerInnerMetadata21 : innerInnerMetadata20,
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/B$C$useSomeInterface$1 null null",
            "",
            "  // access flags 0x1010",
            "  final synthetic Lcom/example/buck/A; $a",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;)V",
            "   L0",
            "    ALOAD 0",
            "    ALOAD 1",
            "    PUTFIELD com/example/buck/B$C$useSomeInterface$1.$a : Lcom/example/buck/A;",
            "   L1",
            "    LINENUMBER 6 L1",
            "    ALOAD 0",
            "    INVOKESPECIAL java/lang/Object.<init> ()V",
            "    RETURN",
            "   L2",
            "    LOCALVARIABLE this Lcom/example/buck/B$C$useSomeInterface$1; L0 L2 0",
            "    LOCALVARIABLE $a Lcom/example/buck/A; L0 L2 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x1",
            "  public someMethod()Lcom/example/buck/A;",
            "   L0",
            "    LINENUMBER 7 L0",
            "    ALOAD 0",
            "    GETFIELD com/example/buck/B$C$useSomeInterface$1.$a : Lcom/example/buck/A;",
            "    ARETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/B$C$useSomeInterface$1; L0 L1 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 1",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "C.kt",
            "package com.example.buck",
            "class C {",
            "fun useInlineExtension() { B.C().useSomeInterface() }",
            "}")
        .testCanCompile();
  }

  @Test
  public void kotlinClassWithInlineExtensionMethodThatUsesRunnable() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T53836707 the methods in the synthetic class are wrongly stripped, preventing
      //  compilation
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=2, xi=48, d1={\"\\u0000\\u0018\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0009\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0000\\u001a(\\u0010\\u0000\\u001a\\u00020\\u0001*\\u00020\\u00022\\u0006\\u0010\\u0003\\u001a\\u00020\\u00042\\u000e\\u0008\\u0008\\u0010\\u0005\\u001a\\u0008\\u0012\\u0004\\u0012\\u00020\\u00010\\u0006H\\u0086\\u0008\\u00f8\\u0001\\u0000\\u001a"
            + " \\u0010\\u0007\\u001a\\u00020\\u0001*\\u00020\\u00022\\u0006\\u0010\\u0003\\u001a\\u00020\\u00042\\u000c\\u0010\\u0005\\u001a\\u0008\\u0012\\u0004\\u0012\\u00020\\u00010\\u0006\\u0082\\u0002\\u0007\\n"
            + "\\u0005\\u0008\\u009920\\u0001\"}, d2={\"postDelayed\", \"\","
            + " \"Lcom/example/buck/A;\", \"delay\", \"\", \"block\", \"Lkotlin/Function0;\","
            + " \"postDelayedNotInlined\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=2, xi=48, d1={\"\\u0000\\u0018\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0002\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0010\\u0009\\n"
            + "\\u0000\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0000\\u001a(\\u0010\\u0000\\u001a\\u00020\\u0001*\\u00020\\u00022\\u0006\\u0010\\u0003\\u001a\\u00020\\u00042\\u000e\\u0008\\u0008\\u0010\\u0005\\u001a\\u0008\\u0012\\u0004\\u0012\\u00020\\u00010\\u0006H\\u0086\\u0008\\u00f8\\u0001\\u0000\\u001a"
            + " \\u0010\\u0007\\u001a\\u00020\\u0001*\\u00020\\u00022\\u0006\\u0010\\u0003\\u001a\\u00020\\u00042\\u000c\\u0010\\u0005\\u001a\\u0008\\u0012\\u0004\\u0012\\u00020\\u00010\\u0006\\u0082\\u0002\\u0007\\n"
            + "\\u0005\\u0008\\u009920\\u0001\"}, d2={\"postDelayed\", \"\","
            + " \"Lcom/example/buck/A;\", \"delay\", \"\", \"block\", \"Lkotlin/Function0;\","
            + " \"postDelayedNotInlined\"})";
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public final boolean postDelayed(Runnable r, long delayMillis) {",
            "    throw new RuntimeException(\"Stub!\");",
            "  }",
            "}")
        .setLanguage(Language.JAVA)
        .createStubJar()
        .setLanguage(Language.KOTLIN)
        .addStubJarToClasspath()
        .setSourceFile(
            "B.kt",
            "package com.example.buck",
            "inline fun A.postDelayed(delay: Long, noinline block: () -> Unit) { postDelayed(block,"
                + " delay) }",
            "fun A.postDelayedNotInlined(delay: Long, block: () -> Unit) { postDelayed(block,"
                + " delay) }")
        .addExpectedStub(
            "com/example/buck/BKt",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "public final class com/example/buck/BKt {",
            "",
            "  // compiled from: B.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x19",
            "  // signature"
                + " (Lcom/example/buck/A;JLkotlin/jvm/functions/Function0<Lkotlin/Unit;>;)V",
            "  // declaration: void postDelayed(com.example.buck.A, long,"
                + " kotlin.jvm.functions.Function0<kotlin.Unit>)",
            "  public final static"
                + " postDelayed(Lcom/example/buck/A;JLkotlin/jvm/functions/Function0;)V",
            "    // annotable parameter count: 3 (invisible)",
            "    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0",
            "    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 2",
            "   L0",
            "    ALOAD 0",
            "    LDC \"<this>\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "    ALOAD 3",
            "    LDC \"block\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "    ICONST_0",
            "    ISTORE 4",
            "   L1",
            "    LINENUMBER 2 L1",
            "    ALOAD 0",
            "    NEW com/example/buck/BKt$sam$i$java_lang_Runnable$0",
            "    DUP",
            "    ALOAD 3",
            "    INVOKESPECIAL com/example/buck/BKt$sam$i$java_lang_Runnable$0.<init>"
                + " (Lkotlin/jvm/functions/Function0;)V",
            "    CHECKCAST java/lang/Runnable",
            "    LLOAD 1",
            "    INVOKEVIRTUAL com/example/buck/A.postDelayed (Ljava/lang/Runnable;J)Z",
            "    POP",
            "    RETURN",
            "   L2",
            "    LOCALVARIABLE $i$f$postDelayed I L1 L2 4",
            "    LOCALVARIABLE $this$postDelayed Lcom/example/buck/A; L0 L2 0",
            "    LOCALVARIABLE delay J L0 L2 1",
            "    LOCALVARIABLE block Lkotlin/jvm/functions/Function0; L0 L2 3",
            "    MAXSTACK = 4",
            "    MAXLOCALS = 5",
            "",
            "  // access flags 0x19",
            "  // signature"
                + " (Lcom/example/buck/A;JLkotlin/jvm/functions/Function0<Lkotlin/Unit;>;)V",
            "  // declaration: void postDelayedNotInlined(com.example.buck.A, long,"
                + " kotlin.jvm.functions.Function0<kotlin.Unit>)",
            "  public final static"
                + " postDelayedNotInlined(Lcom/example/buck/A;JLkotlin/jvm/functions/Function0;)V",
            "    // annotable parameter count: 3 (invisible)",
            "    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0",
            "    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 2",
            "",
            "  // access flags 0x2",
            "  private <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/BKt$sam$i$java_lang_Runnable$0",
            "// class version 52.0 (52)",
            "// access flags 0x1031",
            "public final synthetic class com/example/buck/BKt$sam$i$java_lang_Runnable$0"
                + " implements java/lang/Runnable {",
            "",
            "  // compiled from: B.kt",
            "  OUTERCLASS com/example/buck/BKt null",
            "",
            isKotlin21()
                ? "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=3, xi=176)"
                : "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=3, xi=176)",
            "  // access flags 0x19",
            "  public final static INNERCLASS com/example/buck/BKt$sam$i$java_lang_Runnable$0 null"
                + " null",
            "",
            "  // access flags 0x1012",
            "  private final synthetic Lkotlin/jvm/functions/Function0; function",
            "",
            "  // access flags 0x1",
            "  public <init>(Lkotlin/jvm/functions/Function0;)V",
            "   L0",
            "    ALOAD 1",
            "    LDC \"function\"",
            "    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter"
                + " (Ljava/lang/Object;Ljava/lang/String;)V",
            "    ALOAD 0",
            "    INVOKESPECIAL java/lang/Object.<init> ()V",
            "    ALOAD 0",
            "    ALOAD 1",
            "    PUTFIELD com/example/buck/BKt$sam$i$java_lang_Runnable$0.function :"
                + " Lkotlin/jvm/functions/Function0;",
            "    RETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/BKt$sam$i$java_lang_Runnable$0; L0 L1 0",
            "    LOCALVARIABLE function Lkotlin/jvm/functions/Function0; L0 L1 1",
            "    MAXSTACK = 2",
            "    MAXLOCALS = 2",
            "",
            "  // access flags 0x1011",
            "  public final synthetic run()V",
            "   L0",
            "    ALOAD 0",
            "    GETFIELD com/example/buck/BKt$sam$i$java_lang_Runnable$0.function :"
                + " Lkotlin/jvm/functions/Function0;",
            "    INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object; (itf)",
            "    POP",
            "    RETURN",
            "   L1",
            "    LOCALVARIABLE this Lcom/example/buck/BKt$sam$i$java_lang_Runnable$0; L0 L1 0",
            "    MAXSTACK = 1",
            "    MAXLOCALS = 1",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsOverloadedMethods() throws IOException {
    tester
        .setSourceFile(
            "Dependency.java", "package com.example.buck;", "public class Dependency {", "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "Dependency2.java", "package com.example.buck;", "public class Dependency2 {", "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public void overloaded(Dependency d) { }",
            "  public void overloaded(String s) { }",
            "  public void overloaded(Dependency2 d) { }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public overloaded(Lcom/example/buck/Dependency;)V",
            "",
            "  // access flags 0x1",
            "  public overloaded(Ljava/lang/String;)V",
            "",
            "  // access flags 0x1",
            "  public overloaded(Lcom/example/buck/Dependency2;)V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesThrowsClauses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public void throwSomeStuff() throws Exception, Throwable {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public throwSomeStuff()V throws java/lang/Exception java/lang/Throwable",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesThrowsClausesWithTypeVars() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.io.IOException;",
            "public class A {",
            "  public <E extends IOException> void throwSomeStuff() throws E {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  // signature <E:Ljava/io/IOException;>()V^TE;",
            "  // declaration: void throwSomeStuff<E extends java.io.IOException>() throws E",
            "  public throwSomeStuff()V throws java/io/IOException",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void genericClassSignaturesShouldBePreserved() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A<T> {",
            "  public T get(String key) { return null; }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "// signature <T:Ljava/lang/Object;>Ljava/lang/Object;",
            "// declaration: com/example/buck/A<T>",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  // signature (Ljava/lang/String;)TT;",
            "  // declaration: T get(java.lang.String)",
            "  public get(Ljava/lang/String;)Ljava/lang/Object;",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void genericTypeBoundsShouldBePreserved() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A<T extends java.util.ArrayList, U extends CharSequence, V extends T> {",
            "  public T get(U u, V v) { return null; }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "// signature"
                + " <T:Ljava/util/ArrayList;U::Ljava/lang/CharSequence;V:TT;>Ljava/lang/Object;",
            "// declaration: com/example/buck/A<T extends java.util.ArrayList, U extends"
                + " java.lang.CharSequence, V extends T>",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  // signature (TU;TV;)TT;",
            "  // declaration: T get(U, V)",
            "  public get(Ljava/lang/CharSequence;Ljava/util/ArrayList;)Ljava/util/ArrayList;",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void elementsInStubCorrectlyInOrder() throws IOException {
    // Fields and methods should stub in order
    // Inner classes should stub in reverse
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "    boolean first;",
            "    float second;",
            "  public void foo() { }",
            "  public class B { }",
            "  public class C { }",
            "  public void bar() { }",
            "    public F.H third;",
            "  public class D { }",
            "    public F.G fourth;",
            "    int between;",
            "  public class E {",
            "    public void hello() { }",
            "    public void test() { }",
            "  }",
            "}",
            "class F {",
            "  class G { }",
            "  class H { }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A$C",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$C {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$C com/example/buck/A C",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A$D",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$D {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$D com/example/buck/A D",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A$E",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$E {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$E com/example/buck/A E",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;)V",
            "",
            "  // access flags 0x1",
            "  public hello()V",
            "",
            "  // access flags 0x1",
            "  public test()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "JDK11:  NESTMEMBER com/example/buck/A$C",
            "JDK11:  NESTMEMBER com/example/buck/A$D",
            "JDK11:  NESTMEMBER com/example/buck/A$E",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$E com/example/buck/A E",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$D com/example/buck/A D",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$C com/example/buck/A C",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/F$G com/example/buck/F G",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/F$H com/example/buck/F H",
            "",
            "  // access flags 0x0",
            "  Z first",
            "",
            "  // access flags 0x0",
            "  F second",
            "",
            "  // access flags 0x1",
            "  public Lcom/example/buck/F$H; third",
            "",
            "  // access flags 0x1",
            "  public Lcom/example/buck/F$G; fourth",
            "",
            "  // access flags 0x0",
            "  I between",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public foo()V",
            "",
            "  // access flags 0x1",
            "  public bar()V",
            "}")
        .addExpectedStub(
            "com/example/buck/F$G",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/F$G {",
            "",
            "JDK11:  NESTHOST com/example/buck/F",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/F$G com/example/buck/F G",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/F;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/F$H",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/F$H {",
            "",
            "JDK11:  NESTHOST com/example/buck/F",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/F$H com/example/buck/F H",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/F;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/F",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/F {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/F$G",
            "JDK11:  NESTMEMBER com/example/buck/F$H",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/F$H com/example/buck/F H",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/F$G com/example/buck/F G",
            "",
            "  // access flags 0x0",
            "  <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void genericInterfaceSignaturesShouldBePreserved() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public interface A<T> {",
            "  T get(String key);",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x601",
            "// signature <T:Ljava/lang/Object;>Ljava/lang/Object;",
            "// declaration: com/example/buck/A<T>",
            "public abstract interface com/example/buck/A {",
            "",
            "",
            "  // access flags 0x401",
            "  // signature (Ljava/lang/String;)TT;",
            "  // declaration: T get(java.lang.String)",
            "  public abstract get(Ljava/lang/String;)Ljava/lang/Object;",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void failsWhenInterfaceWillNotLoad() throws IOException {
    if (!testingMode.equals(MODE_SOURCE_BASED_MISSING_DEPS)) {
      return;
    }

    tester
        .setSourceFile(
            "Dep2.java", "package com.example.buck.dependency;", "public interface Dep2 { }")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "Dep.java",
            "package com.example.buck.dependency;",
            "public class Dep implements Dep2 {",
            "  public interface Inner { }",
            "}")
        .compileFullJar()
        // We add Dep to the classpath even for no-deps mode, but its interface Dep2 will be absent.
        // That will cause Dep to fail to load, resulting in Inner not even getting added to the
        // interfaces list by the compiler.
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            // This next import triggers a bug in javac whereby the inability to load the super
            // of Dep gets marked a "recoverable" error, even though it's not
            "import static com.example.buck.otherdep.NonExistent.Member;",
            "import com.example.buck.dependency.Dep;",
            "public abstract class A implements Dep.Inner, Runnable { }")
        .addExpectedCompileError(
            "A.java:4: error: cannot access com.example.buck.dependency.Dep2\n"
                + "public abstract class A implements Dep.Inner, Runnable { }\n"
                + "                                      ^\n"
                + "  class file for com.example.buck.dependency.Dep2 not found")
        .createStubJar();
  }

  @Test
  public void failsWhenClassWillNotLoad() throws IOException {
    if (!testingMode.equals(MODE_SOURCE_BASED_MISSING_DEPS)) {
      return;
    }

    tester
        .setSourceFile(
            "Dep2.java", "package com.example.buck.dependency;", "public interface Dep2 { }")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "Dep.java",
            "package com.example.buck.dependency;",
            "public class Dep implements Dep2 {",
            "  public static class Inner { }",
            "}")
        .compileFullJar()
        // We add Dep to the classpath even for no-deps mode, but its interface Dep2 will be absent.
        // That will cause Dep to fail to load, resulting in Inner not even getting added to the
        // interfaces list by the compiler.
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            // This next import triggers a bug in javac whereby the inability to load the super
            // of Dep gets marked a "recoverable" error, even though it's not
            "import static com.example.buck.otherdep.NonExistent.Member;",
            "import com.example.buck.dependency.Dep;",
            "public class A extends Dep.Inner { }")
        .addExpectedCompileError(
            "A.java:4: error: cannot access com.example.buck.dependency.Dep2\n"
                + "public class A extends Dep.Inner { }\n"
                + "                          ^\n"
                + "  class file for com.example.buck.dependency.Dep2 not found")
        .createStubJar();
  }

  @Test
  @Ignore
  public void failsWhenAnnotationWillNotLoad() throws IOException {
    if (!testingMode.equals(MODE_SOURCE_BASED_MISSING_DEPS)) {
      return;
    }

    tester
        .setSourceFile(
            "DepAnno.java", "package com.example.buck.dependency;", "public @interface DepAnno { }")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import com.example.buck.dependency.DepAnno;",
            "@DepAnno",
            "public class A {",
            "  public void foo(@DepAnno int d) { }",
            "}")
        .addExpectedCompileError(
            "A.java:3: error: Could not find the annotation com.example.buck.dependency.DepAnno.\n"
                + "@DepAnno\n"
                + "^\n"
                + "  This can happen for one of two reasons:\n"
                + "  1. A dependency is missing in the BUCK file for the current target. Try"
                + " building the current rule without the #source-only-abi flavor, fix any errors"
                + " that are reported, and then build this flavor again.\n"
                + "  2. The rule that owns com.example.buck.dependency.DepAnno is not marked with"
                + " required_for_source_only_abi = True. Add that parameter to the rule and try"
                + " again.")
        .addExpectedCompileError(
            "A.java:5: error: Could not find the annotation com.example.buck.dependency.DepAnno.\n"
                + "  public void foo(@DepAnno int d) { }\n"
                + "                  ^\n"
                + "  This can happen for one of two reasons:\n"
                + "  1. A dependency is missing in the BUCK file for the current target. Try"
                + " building the current rule without the #source-only-abi flavor, fix any errors"
                + " that are reported, and then build this flavor again.\n"
                + "  2. The rule that owns com.example.buck.dependency.DepAnno is not marked with"
                + " required_for_source_only_abi = True. Add that parameter to the rule and try"
                + " again.")
        .createStubJar();
  }

  /**
   * Regression test for a bug where our error suppressing listener wasn't tracking Context changes
   * across rounds.
   */
  @Test
  public void suppressesErrorsEvenWithMultipleRounds() throws IOException {
    tester
        .setSourceFile("Dep.java", "package com.example.buck.dep;", "public class Dep { }")
        .compileFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import com.example.buck.dep.Dep;",
            "public class A extends Dep {",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A extends com/example/buck/dep/Dep {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        // Having an AP issue a warning causes the listener to warm up during the first roun, thus
        // exposing the bug
        .setIssueAnnotationProcessorWarnings(true)
        .createAndCheckStubJar();
  }

  @Test
  public void shouldPreserveAnnotationsEvenWhenSuperclassWillNotLoad() throws IOException {
    tester
        .setSourceFile(
            "Dep2.java", "package com.example.buck.dependency;", "public interface Dep2 { }")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "Dep.java",
            "package com.example.buck.dependency;",
            "public class Dep implements Dep2 {",
            "  public static class Inner { }",
            "  public @interface Anno { }",
            "}")
        .compileFullJar()
        // We add Dep to the classpath even for no-deps mode, but its interface Dep2 will be absent.
        // That will cause Dep to fail to load, resulting in Inner not even getting added to the
        // interfaces list by the compiler.
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import com.example.buck.dependency.Dep;",
            "@Dep.Anno",
            "public class A { }")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  @Lcom/example/buck/dependency/Dep$Anno;() // invisible",
            "  // access flags 0x2609",
            "  public static abstract INNERCLASS com/example/buck/dependency/Dep$Anno"
                + " com/example/buck/dependency/Dep Anno",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldIgnorePrivateMethods() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  private void privateMethod() {}",
            "  void packageMethod() {}",
            "  protected void protectedMethod() {}",
            "  public void publicMethod() {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x0",
            "  packageMethod()V",
            "",
            "  // access flags 0x4",
            "  protected protectedMethod()V",
            "",
            "  // access flags 0x1",
            "  public publicMethod()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldGenerateConstructorForClassWithSinglePrivateConstructor() throws IOException {
    tester
        .setSourceFile(
            "A.java", "package com.example.buck;", "public class A {", "  private A() { }", "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x2",
            "  private <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldGenerateConstructorForClassWithPrivateConstructorsOnly() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  private A() { }",
            "  private A(int test) { }",
            "  private A(String test) { }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x2",
            "  private <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldGeneratePrivateInnerClassDefaultConstructor() throws IOException {
    tester
        .setSourceFile(
            "Outer.java",
            "package com.example.buck;",
            "public class Outer {",
            "  public class Inner {",
            "    private Inner() { }",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/Outer$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/Outer$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/Outer",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
            "",
            "  // access flags 0x2",
            "  private <init>(Lcom/example/buck/Outer;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/Outer",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/Outer {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/Outer$Inner",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldGeneratePrivateNestedClassDefaultConstructor() throws IOException {
    tester
        .setSourceFile(
            "Outer.java",
            "package com.example.buck;",
            "public class Outer {",
            "  public class Inner {",
            "    public class Nested {",
            "      private Nested() { }",
            "    }",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/Outer$Inner$Nested",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/Outer$Inner$Nested {",
            "",
            "JDK11:  NESTHOST com/example/buck/Outer",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/Outer$Inner$Nested com/example/buck/Outer$Inner"
                + " Nested",
            "",
            "  // access flags 0x2",
            "  private <init>(Lcom/example/buck/Outer$Inner;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/Outer$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/Outer$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/Outer",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/Outer$Inner$Nested com/example/buck/Outer$Inner"
                + " Nested",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/Outer;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/Outer",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/Outer {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/Outer$Inner",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldPreserveAField() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  protected String protectedField;",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x4",
            "  protected Ljava/lang/String; protectedField",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldIgnorePrivateFields() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  private String privateField;",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldPreserveGenericTypesOnFields() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A<T> {",
            "  public T theField;",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "// signature <T:Ljava/lang/Object;>Ljava/lang/Object;",
            "// declaration: com/example/buck/A<T>",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  // signature TT;",
            "  // declaration: theField extends T",
            "  public Ljava/lang/Object; theField",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldPreserveGenericTypesOnMethods() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A<T> {",
            "  public T get(String key) { return null; }",
            "  public <X extends Comparable<T>> X compareWith(T other) { return null; }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "// signature <T:Ljava/lang/Object;>Ljava/lang/Object;",
            "// declaration: com/example/buck/A<T>",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  // signature (Ljava/lang/String;)TT;",
            "  // declaration: T get(java.lang.String)",
            "  public get(Ljava/lang/String;)Ljava/lang/Object;",
            "",
            "  // access flags 0x1",
            "  // signature <X::Ljava/lang/Comparable<TT;>;>(TT;)TX;",
            "  // declaration: X compareWith<X extends java.lang.Comparable<T>>(T)",
            "  public compareWith(Ljava/lang/Object;)Ljava/lang/Comparable;",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  @Ignore
  public void providesNiceErrorWhenAnnotationMissing() throws IOException {
    if (!testingMode.equals(MODE_SOURCE_BASED_MISSING_DEPS)) {
      return;
    }

    createAnnotationFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  @Foo",
            "  public void cheese(String key) {}",
            "}")
        .addExpectedCompileError(
            "A.java:3: error: Could not find the annotation com.example.buck.Foo.\n"
                + "  @Foo\n"
                + "  ^\n"
                + "  This can happen for one of two reasons:\n"
                + "  1. A dependency is missing in the BUCK file for the current target. Try"
                + " building the current rule without the #source-only-abi flavor, fix any errors"
                + " that are reported, and then build this flavor again.\n"
                + "  2. The rule that owns com.example.buck.Foo is not marked with"
                + " required_for_source_only_abi = True. Add that parameter to the rule and try"
                + " again.")
        .createStubJar();
  }

  @Test
  public void preservesAnnotationsOnMethods() throws IOException {
    notYetImplementedForMissingClasspath();

    createAnnotationFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  @Foo",
            "  public void cheese(String key) {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public cheese(Ljava/lang/String;)V",
            "  @Lcom/example/buck/Foo;()",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsOnFields() throws IOException {
    notYetImplementedForMissingClasspath();

    createAnnotationFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  @Foo",
            "  public String name;",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public Ljava/lang/String; name",
            "  @Lcom/example/buck/Foo;()",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsOnParameters() throws IOException {
    notYetImplementedForMissingClasspath();

    createAnnotationFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public void peynir(@Foo String very, int tasty) {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public peynir(Ljava/lang/String;I)V",
            "    // annotable parameter count: 2 (visible)",
            "    @Lcom/example/buck/Foo;() // parameter 0",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesTypeAnnotationsInClasses() throws IOException {
    createAnnotationFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java", "package com.example.buck;", "public class A<@Foo.TypeAnnotation T> { }")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "// signature <T:Ljava/lang/Object;>Ljava/lang/Object;",
            "// declaration: com/example/buck/A<T>",
            "public class com/example/buck/A {",
            "",
            "",
            "  @Lcom/example/buck/Foo$TypeAnnotation;() : CLASS_TYPE_PARAMETER 0, null //"
                + " invisible",
            "  // access flags 0x2609",
            "  public static abstract INNERCLASS com/example/buck/Foo$TypeAnnotation"
                + " com/example/buck/Foo TypeAnnotation",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesTypeAnnotationsInMethods() throws IOException {
    createAnnotationFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.util.List;",
            "public class A {",
            "  <@Foo.TypeAnnotation T> void foo(@Foo.TypeAnnotation String s) { }",
            "  @Foo.TypeAnnotation List<String> bar(@Foo.TypeAnnotation Integer i,"
                + " List<List<@Foo.TypeAnnotation String>> s) { return null; }",
            "  List<@Foo.TypeAnnotation String> baz() { return null; }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // access flags 0x2609",
            "  public static abstract INNERCLASS com/example/buck/Foo$TypeAnnotation"
                + " com/example/buck/Foo TypeAnnotation",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x0",
            "  // signature <T:Ljava/lang/Object;>(Ljava/lang/String;)V",
            "  // declaration: void foo<T>(java.lang.String)",
            "  foo(Ljava/lang/String;)V",
            "  @Lcom/example/buck/Foo$TypeAnnotation;() : METHOD_TYPE_PARAMETER 0, null //"
                + " invisible",
            "  @Lcom/example/buck/Foo$TypeAnnotation;() : METHOD_FORMAL_PARAMETER 0, null //"
                + " invisible",
            "",
            "  // access flags 0x0",
            "  // signature"
                + " (Ljava/lang/Integer;Ljava/util/List<Ljava/util/List<Ljava/lang/String;>;>;)Ljava/util/List<Ljava/lang/String;>;",
            "  // declaration: java.util.List<java.lang.String> bar(java.lang.Integer,"
                + " java.util.List<java.util.List<java.lang.String>>)",
            "  bar(Ljava/lang/Integer;Ljava/util/List;)Ljava/util/List;",
            "  @Lcom/example/buck/Foo$TypeAnnotation;() : METHOD_RETURN, null // invisible",
            "  @Lcom/example/buck/Foo$TypeAnnotation;() : METHOD_FORMAL_PARAMETER 0, null //"
                + " invisible",
            "  @Lcom/example/buck/Foo$TypeAnnotation;() : METHOD_FORMAL_PARAMETER 1, 0;0; //"
                + " invisible",
            "",
            "  // access flags 0x0",
            "  // signature ()Ljava/util/List<Ljava/lang/String;>;",
            "  // declaration: java.util.List<java.lang.String> baz()",
            "  baz()Ljava/util/List;",
            "  @Lcom/example/buck/Foo$TypeAnnotation;() : METHOD_RETURN, 0; // invisible",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesTypeAnnotationsInFields() throws IOException {
    createAnnotationFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.util.List;",
            "public class A {",
            "  List<@Foo.TypeAnnotation String> list;",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // access flags 0x2609",
            "  public static abstract INNERCLASS com/example/buck/Foo$TypeAnnotation"
                + " com/example/buck/Foo TypeAnnotation",
            "",
            "  // access flags 0x0",
            "  // signature Ljava/util/List<Ljava/lang/String;>;",
            "  // declaration: list extends java.util.List<java.lang.String>",
            "  Ljava/util/List; list",
            "  @Lcom/example/buck/Foo$TypeAnnotation;() : FIELD, 0; // invisible",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void omitsAnnotationsWithSourceRetention() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "@SourceRetentionAnno()",
            "public class A { }",
            "@Retention(RetentionPolicy.SOURCE)",
            "@interface SourceRetentionAnno { }")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/SourceRetentionAnno",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2600",
            "abstract @interface com/example/buck/SourceRetentionAnno implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.SOURCE)",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsWithClassRetention() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "@ClassRetentionAnno()",
            "public class A { }",
            "@Retention(RetentionPolicy.CLASS)",
            "@interface ClassRetentionAnno { }")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  @Lcom/example/buck/ClassRetentionAnno;() // invisible",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/ClassRetentionAnno",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2600",
            "abstract @interface com/example/buck/ClassRetentionAnno implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.CLASS)",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsWithRuntimeRetention() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "@RuntimeRetentionAnno()",
            "public class A { }",
            "@Retention(RetentionPolicy.RUNTIME)",
            "@interface RuntimeRetentionAnno { }")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  @Lcom/example/buck/RuntimeRetentionAnno;()",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/RuntimeRetentionAnno",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2600",
            "abstract @interface com/example/buck/RuntimeRetentionAnno implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsWithPrimitiveValues() throws IOException {
    notYetImplementedForMissingClasspath();

    createAnnotationFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "@Foo(primitiveValue=1)",
            "public @interface A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2601",
            "public abstract @interface com/example/buck/A implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Lcom/example/buck/Foo;(primitiveValue=1)",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsWithStringArrayValues() throws IOException {
    notYetImplementedForMissingClasspath();

    createAnnotationFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "@Foo(stringArrayValue={\"1\", \"2\"})",
            "public @interface A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2601",
            "public abstract @interface com/example/buck/A implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Lcom/example/buck/Foo;(stringArrayValue={\"1\", \"2\"})",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsWithEnumValues() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "@Retention(RetentionPolicy.RUNTIME)",
            "public @interface A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2601",
            "public abstract @interface com/example/buck/A implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsWithTypeValues() throws IOException {
    createAnnotationFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "Dependency.java", "package com.example.buck;", "public class Dependency { }")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "@Foo(typeValue=Dependency.class)",
            "public @interface A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2601",
            "public abstract @interface com/example/buck/A implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Lcom/example/buck/Foo;(typeValue=com.example.buck.Dependency.class)",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsWithConstrainedTypeValues() throws IOException {
    createAnnotationFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "Dependency.java",
            "package com.example.buck;",
            "public interface Dependency extends Runnable { }")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "@Foo(runnableValues={Dependency.class})",
            "public @interface A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2601",
            "public abstract @interface com/example/buck/A implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Lcom/example/buck/Foo;(runnableValues={com.example.buck.Dependency.class})",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void providesNiceErrorWhenConstantMissing() throws IOException {
    if (!testingMode.equals(MODE_SOURCE_BASED_MISSING_DEPS)) {
      return;
    }

    createAnnotationFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "Dependency.java",
            "package com.example.buck;",
            "public class Dependency { public static final String STRING = \"string\";}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "@Foo(stringValue=Dependency.STRING)",
            "public @interface A {}")
        .addExpectedCompileError(
            "A.java:2: error: Could not resolve constant. Either inline the value or add"
                + " required_for_source_only_abi = True to the build rule that contains it.\n"
                + "@Foo(stringValue=Dependency.STRING)\n"
                + "                           ^")
        .createStubJar();
  }

  @Test
  public void preservesAnnotationsWithConstantValues() throws IOException {
    notYetImplementedForMissingClasspath();

    createAnnotationFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "Dependency.java",
            "package com.example.buck;",
            "public class Dependency { public static final String STRING = \"string\";}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "@Foo(stringValue=Dependency.STRING)",
            "public @interface A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2601",
            "public abstract @interface com/example/buck/A implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Lcom/example/buck/Foo;(stringValue=\"string\")",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsWithEnumArrayValues() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "@Target({ElementType.CONSTRUCTOR, ElementType.FIELD})",
            "public @interface A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2601",
            "public abstract @interface com/example/buck/A implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Ljava/lang/annotation/Target;(value={Ljava/lang/annotation/ElementType;.CONSTRUCTOR,"
                + " Ljava/lang/annotation/ElementType;.FIELD})",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsWithAnnotationValues() throws IOException {
    notYetImplementedForMissingClasspath();

    createAnnotationFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "@Foo(annotationValue=@Retention(RetentionPolicy.RUNTIME))",
            "public @interface A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2601",
            "public abstract @interface com/example/buck/A implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Lcom/example/buck/Foo;(annotationValue=@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME))",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationsWithAnnotationArrayValues() throws IOException {
    notYetImplementedForMissingClasspath();

    createAnnotationFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "@Foo(annotationArrayValue=@Retention(RetentionPolicy.RUNTIME))",
            "public @interface A {}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2601",
            "public abstract @interface com/example/buck/A implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Lcom/example/buck/Foo;(annotationArrayValue={@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)})",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesAnnotationDefaultValues() throws IOException {
    tester
        .setSourceFile(
            "Dependency.java", "package com.example.buck;", "public class Dependency { }")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "Foo.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "public @interface Foo {",
            "  int primitiveValue() default 42;",
            "  String[] stringArrayValue() default {\"Hello\", \"World\", \"!\"};",
            "  Retention annotationValue() default @Retention(RetentionPolicy.SOURCE);",
            "  Retention[] annotationArrayValue() default {@Retention(RetentionPolicy.SOURCE),"
                + " @Retention(RetentionPolicy.CLASS), @Retention(RetentionPolicy.RUNTIME)};",
            "  RetentionPolicy enumValue () default RetentionPolicy.CLASS;",
            "  Class typeValue() default Dependency.class;",
            "}")
        .addExpectedStub(
            "com/example/buck/Foo",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2601",
            "public abstract @interface com/example/buck/Foo implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  // access flags 0x401",
            "  public abstract primitiveValue()I",
            "    default=42",
            "",
            "  // access flags 0x401",
            "  public abstract stringArrayValue()[Ljava/lang/String;",
            "    default={\"Hello\", \"World\", \"!\"}",
            "",
            "  // access flags 0x401",
            "  public abstract annotationValue()Ljava/lang/annotation/Retention;",
            "    default=@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.SOURCE)",
            "",
            "  // access flags 0x401",
            "  public abstract annotationArrayValue()[Ljava/lang/annotation/Retention;",
            "    default={@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.SOURCE),"
                + " @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.CLASS),"
                + " @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)}",
            "",
            "  // access flags 0x401",
            "  public abstract enumValue()Ljava/lang/annotation/RetentionPolicy;",
            "    default=Ljava/lang/annotation/RetentionPolicy;.CLASS",
            "",
            "  // access flags 0x401",
            "  public abstract typeValue()Ljava/lang/Class;",
            "    default=com.example.buck.Dependency.class",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsEnums() throws IOException {
    tester
        .setSourceFile(
            "A.java", "package com.example.buck;", "public enum A {", "  Value1,", "  Value2", "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4031",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;",
            "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>",
            "public final enum com/example/buck/A extends java/lang/Enum {",
            "",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A; Value1",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A; Value2",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
            "",
            "  // access flags 0x2",
            "  private <init>(Ljava/lang/String;I)V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsEnumsOverridingGenericInterface() throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public enum A implements java.util.Comparator<A> {",
            "  Value1,",
            "  Value2;",
            "  @Override",
            "  public int compare(A a1, A a2) { return 0; }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4031",
            "// signature"
                + " Ljava/lang/Enum<Lcom/example/buck/A;>;Ljava/util/Comparator<Lcom/example/buck/A;>;",
            "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>"
                + " implements java.util.Comparator<com.example.buck.A>",
            "public final enum com/example/buck/A extends java/lang/Enum implements"
                + " java/util/Comparator {",
            "",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A; Value1",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A; Value2",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
            "",
            "  // access flags 0x1",
            "  public compare(Lcom/example/buck/A;Lcom/example/buck/A;)I",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge compare(Ljava/lang/Object;Ljava/lang/Object;)I",
            "",
            "  // access flags 0x2",
            "  private <init>(Ljava/lang/String;I)V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsExplicitlyAbstractEnums() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public enum A {",
            "  Value1 {",
            "    @Override",
            "    public void run() { }",
            "  };",
            "  public abstract void run();",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4421",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;",
            "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>",
            // abstract flag is removed in the stub:
            "public abstract enum com/example/buck/A extends java/lang/Enum {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$1",
            "  // access flags 0x4010",
            "  final enum INNERCLASS com/example/buck/A$1 null null",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A; Value1",
            "",
            "  // access flags 0x101A",
            "  private final static synthetic [Lcom/example/buck/A; $VALUES",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
            "",
            "  // access flags 0x2",
            "  // signature ()V",
            "  // declaration: void <init>()",
            "  private <init>(Ljava/lang/String;I)V",
            "",
            "  // access flags 0x401",
            "  public abstract run()V",
            "",
            "  // access flags 0x100A",
            "  private static synthetic $values()[Lcom/example/buck/A;",
            "",
            "JDK8:  // access flags 0x1000",
            "JDK8:  synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V",
            "JDK8:",
            "  // access flags 0x8",
            "  static <clinit>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4421",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;",
            "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>",
            "public abstract enum com/example/buck/A extends java/lang/Enum {",
            "",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A; Value1",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
            "",
            "  // access flags 0x401",
            "  public abstract run()V",
            "",
            "  // access flags 0x2",
            "  private <init>(Ljava/lang/String;I)V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsImplicitlyAbstractEnums() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public enum A implements Runnable {",
            "  Value1 {",
            "    @Override",
            "    public void run() { }",
            "  };",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4421",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;Ljava/lang/Runnable;",
            "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>"
                + " implements java.lang.Runnable",
            // abstract flag is removed in the stub:
            "public abstract enum com/example/buck/A extends java/lang/Enum implements"
                + " java/lang/Runnable {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$1",
            "  // access flags 0x4010",
            "  final enum INNERCLASS com/example/buck/A$1 null null",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A; Value1",
            "",
            "  // access flags 0x101A",
            "  private final static synthetic [Lcom/example/buck/A; $VALUES",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
            "",
            "  // access flags 0x2",
            "  // signature ()V",
            "  // declaration: void <init>()",
            "  private <init>(Ljava/lang/String;I)V",
            "",
            "  // access flags 0x100A",
            "  private static synthetic $values()[Lcom/example/buck/A;",
            "",
            "JDK8:  // access flags 0x1000",
            "JDK8:  synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V",
            "JDK8:",
            "  // access flags 0x8",
            "  static <clinit>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4421",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;Ljava/lang/Runnable;",
            "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>"
                + " implements java.lang.Runnable",
            "public abstract enum com/example/buck/A extends java/lang/Enum implements"
                + " java/lang/Runnable {",
            "",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A; Value1",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
            "",
            "  // access flags 0x2",
            "  private <init>(Ljava/lang/String;I)V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsInnerClasses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public class B {",
            "    public int count;",
            "    public void foo() {}",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public I count",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;)V",
            "",
            "  // access flags 0x1",
            "  public foo()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsProtectedInnerClasses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  protected class B {",
            "    public int count;",
            "    public void foo() {}",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x4",
            "  protected INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public I count",
            "",
            "  // access flags 0x4",
            "  protected <init>(Lcom/example/buck/A;)V",
            "",
            "  // access flags 0x1",
            "  public foo()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "  // access flags 0x4",
            "  protected INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsDefaultInnerClasses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  class B {",
            "    public int count;",
            "    public void foo() {}",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/A$B {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public I count",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/A;)V",
            "",
            "  // access flags 0x1",
            "  public foo()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  // TODO(jkeljo): We should only be stubbing private inner classes when they are referenced in
  // the ABI. That's a more involved change which I'll make soon, but for now I want to document
  // the existing behavior and ensure it is consistent across class and source-based ABIs.
  @Test
  public void stubsPrivateInnerClasses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  private class B {",
            "    public int count;",
            "    public void foo() {}",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/A$B {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x2",
            "  private INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public I count",
            "",
            "  // access flags 0x1",
            "  public foo()V",
            "",
            "  // access flags 0x2",
            "  private <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "  // access flags 0x2",
            "  private INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void kotlinStubsPrivateInnerClasses() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // Source produces some different metadata.
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String innerMetadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0018\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u0008\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u0002\\u0008\\u0002\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0006\\u0010\\u0008\\u001a\\u00020\\u0009R\\u0014\\u0010\\u0004\\u001a\\u00020\\u0005X\\u0086D\\u00a2\\u0006\\u0008\\n"
            + "\\u0000\\u001a\\u0004\\u0008\\u0006\\u0010\\u0007\"},"
            + " d2={\"Lcom/example/buck/A$B;\", \"\", \"<init>\", \"()V\", \"count\", \"\","
            + " \"getCount\", \"()I\", \"foo\", \"\"})";
    String innerMetadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0018\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u0008\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u0002\\u0008\\u0002\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0006\\u0010\\u0008\\u001a\\u00020\\u0009R\\u0014\\u0010\\u0004\\u001a\\u00020\\u0005X\\u0086D\\u00a2\\u0006\\u0008\\n"
            + "\\u0000\\u001a\\u0004\\u0008\\u0006\\u0010\\u0007\"},"
            + " d2={\"Lcom/example/buck/A$B;\", \"\", \"<init>\", \"()V\", \"count\", \"\","
            + " \"getCount\", \"()I\", \"foo\", \"\"})";
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u000c\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001:\\u0001\\u0004B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"B\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u000c\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001:\\u0001\\u0004B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"B\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck;",
            "open class A {",
            "  private class B {",
            "    val count: Int = 0",
            "    fun foo() {",
            "      // some thing here",
            "    }",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "// class version 52.0 (52)",
            "// access flags 0x30",
            "final class com/example/buck/A$B {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? innerMetadata21 : innerMetadata20,
            "  // access flags 0x1A",
            "  private final static INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  public final getCount()I",
            "",
            "  // access flags 0x11",
            "  public final foo()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "  // access flags 0x1A",
            "  private final static INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsInnerEnums() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public enum B { Value }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4031",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A$B;>;",
            "// declaration: com/example/buck/A$B extends java.lang.Enum<com.example.buck.A$B>",
            "public final enum com/example/buck/A$B extends java/lang/Enum {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x4019",
            "  public final static enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A$B; Value",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A$B;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$B;",
            "",
            "  // access flags 0x2",
            "  private <init>(Ljava/lang/String;I)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "  // access flags 0x4019",
            "  public final static enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsAbstractInnerEnums() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public enum B implements Runnable {",
            "    Value {",
            "      @Override",
            "      public void run() {}",
            "    }",
            "  }",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4421",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A$B;>;Ljava/lang/Runnable;",
            "// declaration: com/example/buck/A$B extends java.lang.Enum<com.example.buck.A$B>"
                + " implements java.lang.Runnable",
            "public abstract enum com/example/buck/A$B extends java/lang/Enum implements"
                + " java/lang/Runnable {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x4409",
            "  public static abstract enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "  // access flags 0x4010",
            "  final enum INNERCLASS com/example/buck/A$B$1 null null",
            "JDK8:  // access flags 0x1008",
            "JDK8:  static synthetic INNERCLASS com/example/buck/A$1 null null",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A$B; Value",
            "",
            "  // access flags 0x101A",
            "  private final static synthetic [Lcom/example/buck/A$B; $VALUES",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A$B;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$B;",
            "",
            "  // access flags 0x2",
            "  // signature ()V",
            "  // declaration: void <init>()",
            "  private <init>(Ljava/lang/String;I)V",
            "",
            "  // access flags 0x100A",
            "  private static synthetic $values()[Lcom/example/buck/A$B;",
            "",
            "JDK8:  // access flags 0x1000",
            "JDK8:  synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V",
            "JDK8:",
            "  // access flags 0x8",
            "  static <clinit>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4421",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A$B;>;Ljava/lang/Runnable;",
            "// declaration: com/example/buck/A$B extends java.lang.Enum<com.example.buck.A$B>"
                + " implements java.lang.Runnable",
            "public abstract enum com/example/buck/A$B extends java/lang/Enum implements"
                + " java/lang/Runnable {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x4409",
            "  public static abstract enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A$B; Value",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A$B;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$B;",
            "",
            "  // access flags 0x2",
            "  private <init>(Ljava/lang/String;I)V",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "JDK11:  NESTMEMBER com/example/buck/A$B$1",
            "JDK8:  // access flags 0x1008",
            "JDK8:  static synthetic INNERCLASS com/example/buck/A$1 null null",
            "  // access flags 0x4409",
            "  public static abstract enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "JDK11:  // access flags 0x4010",
            "JDK11:  final enum INNERCLASS com/example/buck/A$B$1 null null",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "  // access flags 0x4409",
            "  public static abstract enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsNestedInnerClasses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public class B {",
            "    public class C {",
            "      public int count;",
            "      public void foo() {}",
            "      public class D { }",
            "    }",
            "  }",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A$B$C$D",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B$C$D {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B$C$D com/example/buck/A$B$C D",
            "",
            "  // access flags 0x1010",
            "  final synthetic Lcom/example/buck/A$B$C; this$2",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A$B$C;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B$C$D",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B$C$D {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B$C$D com/example/buck/A$B$C D",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A$B$C;)V",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A$B$C",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B$C {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B$C$D com/example/buck/A$B$C D",
            "",
            "  // access flags 0x1",
            "  public I count",
            "",
            "  // access flags 0x1010",
            "  final synthetic Lcom/example/buck/A$B; this$1",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A$B;)V",
            "",
            "  // access flags 0x1",
            "  public foo()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B$C",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B$C {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B$C$D com/example/buck/A$B$C D",
            "",
            "  // access flags 0x1",
            "  public I count",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A$B;)V",
            "",
            "  // access flags 0x1",
            "  public foo()V",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C",
            "",
            "  // access flags 0x1010",
            "  final synthetic Lcom/example/buck/A; this$0",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "JDK11:  NESTMEMBER com/example/buck/A$B$C",
            "JDK11:  NESTMEMBER com/example/buck/A$B$C$D",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "JDK11:  // access flags 0x1",
            "JDK11:  public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C",
            "JDK11:  // access flags 0x1",
            "JDK11:  public INNERCLASS com/example/buck/A$B$C$D com/example/buck/A$B$C D",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsReferencesToInnerClassesOfOtherTypes() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  class Inner {",
            "    B.C.D field1;",
            "  }",
            "}",
            "class B {",
            "  public class C {",
            "    public class D { }",
            "  }",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/A$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            // An innerclass entry is present for B$C and B$C$D even though they're not inner
            // classes
            // of A, so that the compiler and runtime know how to interpret the name B$C or B$C$D.
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x0",
            "  Lcom/example/buck/B$C$D; field1",
            "",
            "  // access flags 0x1010",
            "  final synthetic Lcom/example/buck/A; this$0",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/A$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            // Inenrclass entries for references to other classes are sorted. Otherwise the order
            // in class-based ABIs could be influenced by references inside method bodies.
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x0",
            "  Lcom/example/buck/B$C$D; field1",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$Inner",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/B$C$D",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/B$C$D {",
            "",
            "JDK11:  NESTHOST com/example/buck/B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/B$C;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/B$C",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/B$C {",
            "",
            "JDK11:  NESTHOST com/example/buck/B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/B;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/B {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/B$C",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "",
            "  // access flags 0x0",
            "  <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsReferencesToStaticInnerClassesOfOtherTypes() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  class Inner {",
            "    B.C.D field1;",
            "  }",
            "}",
            "class B {",
            "  public static class C {",
            "    public static class D { }",
            "  }",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/A$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            // An innerclass entry is present for B$C and B$C$D even though they're not inner
            // classes
            // of A, so that the compiler and runtime know how to interpret the name B$C or B$C$D.
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x0",
            "  Lcom/example/buck/B$C$D; field1",
            "",
            "  // access flags 0x1010",
            "  final synthetic Lcom/example/buck/A; this$0",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/A$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            // Inenrclass entries for references to other classes are sorted. Otherwise the order
            // in class-based ABIs could be influenced by references inside method bodies.
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x0",
            "  Lcom/example/buck/B$C$D; field1",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$Inner",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/B$C$D",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/B$C$D {",
            "",
            "JDK11:  NESTHOST com/example/buck/B",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/B$C",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/B$C {",
            "",
            "JDK11:  NESTHOST com/example/buck/B",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/B {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/B$C",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "",
            "  // access flags 0x0",
            "  <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsReferencesToInnerEnumsOfOtherTypes() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  class Inner {",
            "    B.C.D field1;",
            "  }",
            "}",
            "class B {",
            "  public static class C {",
            "    public static enum D { Value }",
            "  }",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/A$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            // An innerclass entry is present for B$C and B$C$D even though they're not inner
            // classes
            // of A, so that the compiler and runtime know how to interpret the name B$C or B$C$D.
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x4019",
            "  public final static enum INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x0",
            "  Lcom/example/buck/B$C$D; field1",
            "",
            "  // access flags 0x1010",
            "  final synthetic Lcom/example/buck/A; this$0",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/A$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x4019",
            "  public final static enum INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x0",
            "  Lcom/example/buck/B$C$D; field1",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/A;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$Inner",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/B$C$D",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4031",
            "// signature Ljava/lang/Enum<Lcom/example/buck/B$C$D;>;",
            "// declaration: com/example/buck/B$C$D extends java.lang.Enum<com.example.buck.B$C$D>",
            "public final enum com/example/buck/B$C$D extends java/lang/Enum {",
            "",
            "JDK11:  NESTHOST com/example/buck/B",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x4019",
            "  public final static enum INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/B$C$D; Value",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/B$C$D;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/B$C$D;",
            "",
            "  // access flags 0x2",
            "  private <init>(Ljava/lang/String;I)V",
            "}")
        .addExpectedStub(
            "com/example/buck/B$C",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/B$C {",
            "",
            "JDK11:  NESTHOST com/example/buck/B",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "  // access flags 0x4019",
            "  public final static enum INNERCLASS com/example/buck/B$C$D com/example/buck/B$C D",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/B {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/B$C",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/B$C com/example/buck/B C",
            "",
            "  // access flags 0x0",
            "  <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsReferencesToImportedTypesShadowingStarImportedOnes() throws IOException {
    tester
        .setSourceFile("String.java", "package com.example.buck.shadow;", "public class String { }")
        .compileFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import com.example.buck.shadow.String;",
            "public class A {",
            "  String s;",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x0",
            "  Lcom/example/buck/shadow/String; s",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsReferencesToMemberTypesShadowingImportedOnes() throws IOException {
    tester
        .setSourceFile(
            "State.java", "package com.example.buck.state;", "public @interface State { }")
        .compileFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import com.example.buck.state.State;",
            "@State",
            "public class A {",
            "  State s;",
            "  public static class State { }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$State",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$State {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/A$State com/example/buck/A State",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  @Lcom/example/buck/state/State;() // invisible",
            "JDK11:  NESTMEMBER com/example/buck/A$State",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/A$State com/example/buck/A State",
            "",
            "  // access flags 0x0",
            "  Lcom/example/buck/A$State; s",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsImportedReferencesToInnerClassesOfOtherTypes() throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile(
            "Imported.java",
            "package com.example.buck.imported;",
            "public class Imported {",
            "  public class Inner {",
            "    public class Innerer { }",
            "  }",
            "}")
        .compileFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import com.example.buck.imported.Imported.Inner.Innerer;",
            "public class A {",
            "  public Innerer field;",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/imported/Imported$Inner"
                + " com/example/buck/imported/Imported Inner",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/imported/Imported$Inner$Innerer"
                + " com/example/buck/imported/Imported$Inner Innerer",
            "",
            "  // access flags 0x1",
            "  public Lcom/example/buck/imported/Imported$Inner$Innerer; field",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsStaticImportedReferencesToInnerClassesOfOtherTypes() throws IOException {
    notYetImplementedForMissingClasspath();

    tester
        .setSourceFile(
            "Imported.java",
            "package com.example.buck.imported;",
            "public class Imported {",
            "  public static class Inner {",
            "    public static class Innerer { }",
            "  }",
            "}")
        .compileFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import static com.example.buck.imported.Imported.Inner.Innerer;",
            "public class A {",
            "  public Innerer field;",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/imported/Imported$Inner"
                + " com/example/buck/imported/Imported Inner",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/imported/Imported$Inner$Innerer"
                + " com/example/buck/imported/Imported$Inner Innerer",
            "",
            "  // access flags 0x1",
            "  public Lcom/example/buck/imported/Imported$Inner$Innerer; field",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void detectsStaticImportedReferencesToMissingInnerClassesOfOtherTypes()
      throws IOException {
    tester
        .setSourceFile(
            "ImportedBase.java",
            "package com.example.buck.imported;",
            "public class ImportedBase {",
            "  public static class Inner {",
            "    public static class Innerer { }",
            "  }",
            "}")
        .compileFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "Imported.java",
            "package com.example.buck.imported;",
            "public class Imported extends ImportedBase { }")
        .compileFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import static com.example.buck.imported.Imported.Inner;",
            "public class A {",
            "  public Inner field;",
            "}");

    if (testingMode.equals(MODE_SOURCE_BASED_MISSING_DEPS)) {
      tester
          .addExpectedCompileError(
              "A.java:2: error: cannot find symbol",
              "import static com.example.buck.imported.Imported.Inner;",
              "^",
              "  symbol:   static Inner",
              "  location: class com.example.buck.imported.Imported")
          .createStubJar();
    } else {
      tester
          .addExpectedStub(
              "com/example/buck/A",
              "JDK8:// class version 52.0 (52)",
              "JDK11:// class version 55.0 (55)",
              "// access flags 0x21",
              "public class com/example/buck/A {",
              "",
              "  // access flags 0x9",
              "  public static INNERCLASS com/example/buck/imported/ImportedBase$Inner"
                  + " com/example/buck/imported/ImportedBase Inner",
              "",
              "  // access flags 0x1",
              "  public Lcom/example/buck/imported/ImportedBase$Inner; field",
              "",
              "  // access flags 0x1",
              "  public <init>()V",
              "}")
          .createAndCheckStubJar();
    }
  }

  @Test
  public void failsOnObviouslyNonExistentStaticImports() throws IOException {
    if (!testingMode.equals(MODE_SOURCE_BASED_MISSING_DEPS)) {
      return;
    }
    tester
        .setSourceFile(
            "Imported.java", "package com.example.buck.imported;", "public class Imported { }")
        .compileFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import static com.example.buck.imported.Imported.Inner;",
            "public class A {",
            "  public Inner field;",
            "}")
        .addExpectedCompileError(
            "A.java:2: error: cannot find symbol",
            "import static com.example.buck.imported.Imported.Inner;",
            "^",
            "  symbol:   static Inner",
            "  location: class com.example.buck.imported.Imported")
        .createStubJar();
  }

  @Test
  public void stubsReferencesToInnerClassesOfOtherTypesInAnnotationValues() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "@Anno(B.Inner.class)",
            "public class A {",
            "}",
            "@interface Anno {",
            "  Class<?> value();",
            "}",
            "class B {",
            "  class Inner { }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  @Lcom/example/buck/Anno;(value=com.example.buck.B$Inner.class) // invisible",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/B$Inner com/example/buck/B Inner",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/Anno",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2600",
            "abstract @interface com/example/buck/Anno implements java/lang/annotation/Annotation"
                + " {",
            "",
            "",
            "  // access flags 0x401",
            "  // signature ()Ljava/lang/Class<*>;",
            "  // declaration: java.lang.Class<?> value()",
            "  public abstract value()Ljava/lang/Class;",
            "}")
        .addExpectedStub(
            "com/example/buck/B$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/B$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/B",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/B$Inner com/example/buck/B Inner",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/B;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/B {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/B$Inner",
            "  // access flags 0x0",
            "  INNERCLASS com/example/buck/B$Inner com/example/buck/B Inner",
            "",
            "  // access flags 0x0",
            "  <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void doesNotStubReferencesToInnerClassesFromInsideMethods() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.util.Map;",
            "public class A {",
            "  public void foo(Map<String, String> map) {",
            "    for (Map.Entry<String, String> entry : map.entrySet()) {",
            "      entry.getValue();",
            "    }",
            "  }",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // access flags 0x609",
            // This entry won't be in the stub:
            "  public static abstract INNERCLASS java/util/Map$Entry java/util/Map Entry",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  // signature (Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;)V",
            "  // declaration: void foo(java.util.Map<java.lang.String, java.lang.String>)",
            "  public foo(Ljava/util/Map;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  // signature (Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;)V",
            "  // declaration: void foo(java.util.Map<java.lang.String, java.lang.String>)",
            "  public foo(Ljava/util/Map;)V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsReferencesFromBridgeMethodsToInnerClassesOtherTypes() throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public class B extends C { }",
            "}",
            "class C {",
            "  public D foo() { return new D(); }",
            "  static class D {",
            "  }",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B extends com/example/buck/C {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "  // access flags 0x8",
            "  static INNERCLASS com/example/buck/C$D com/example/buck/C D",
            "",
            "  // access flags 0x1010",
            "  final synthetic Lcom/example/buck/A; this$0",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;)V",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge foo()Lcom/example/buck/C$D;",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B extends com/example/buck/C {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "  // access flags 0x8",
            "  static INNERCLASS com/example/buck/C$D com/example/buck/C D",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;)V",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge foo()Lcom/example/buck/C$D;",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/C$D",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/C$D {",
            "",
            "JDK11:  NESTHOST com/example/buck/C",
            "  // access flags 0x8",
            "  static INNERCLASS com/example/buck/C$D com/example/buck/C D",
            "",
            "  // access flags 0x0",
            "  <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/C",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "class com/example/buck/C {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/C$D",
            "  // access flags 0x8",
            "  static INNERCLASS com/example/buck/C$D com/example/buck/C D",
            "",
            "  // access flags 0x0",
            "  <init>()V",
            "",
            "  // access flags 0x1",
            "  public foo()Lcom/example/buck/C$D;",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubsStaticMemberClasses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public static class B {",
            "    public int count;",
            "    public void foo() {}",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public I count",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public foo()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "  // access flags 0x9",
            "  public static INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void ignoresAnonymousClasses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public Runnable r = new Runnable() {",
            "    public void run() { }",
            "  };",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public Ljava/lang/Runnable; r",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void kotlinIgnoresAnonymousClasses() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // Source does not strip anonymous classes.
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0014\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0008\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003R\\u0011\\u0010\\u0004\\u001a\\u00020\\u0005\\u00a2\\u0006\\u0008\\n"
            + "\\u0000\\u001a\\u0004\\u0008\\u0006\\u0010\\u0007\"}, d2={\"Lcom/example/buck/A;\","
            + " \"\", \"<init>\", \"()V\", \"r\", \"Ljava/lang/Runnable;\", \"getR\","
            + " \"()Ljava/lang/Runnable;\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0014\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0008\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003R\\u0011\\u0010\\u0004\\u001a\\u00020\\u0005\\u00a2\\u0006\\u0008\\n"
            + "\\u0000\\u001a\\u0004\\u0008\\u0006\\u0010\\u0007\"}, d2={\"Lcom/example/buck/A;\","
            + " \"\", \"<init>\", \"()V\", \"r\", \"Ljava/lang/Runnable;\", \"getR\","
            + " \"()Ljava/lang/Runnable;\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck;",
            "open class A {",
            "  val r: Runnable = Runnable() {",
            "    fun run() { }",
            "  };",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  public final getR()Ljava/lang/Runnable;",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void ignoresInnerClassesOfAnonymousClasses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public Runnable r = new Runnable() {",
            "    class Inner { }",
            "    public void run() { }",
            "  };",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public Ljava/lang/Runnable; r",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void ignoresLocalClasses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public void method() {",
            "    class Local { };",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public method()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void kotlinIgnoresLocalClasses() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // Source does not strip local classes.
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0010\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0006\\u0010\\u0004\\u001a\\u00020\\u0005\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"method\", \"\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0010\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u0002\\u0008\\u0016\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0006\\u0010\\u0004\\u001a\\u00020\\u0005\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"method\", \"\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck;",
            "open class A {",
            "  fun method() {",
            "    class Local { }",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x11",
            "  public final method()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesRuntimeInvisibleAnnotationsOnInnerClassConstructorParameters()
      throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public class Inner {",
            "    public Inner(@Anno String param) { }",
            "  }",
            "}",
            "@interface Anno {}")
        .addExpectedStub(
            "com/example/buck/A$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;Ljava/lang/String;)V",
            "    // annotable parameter count: 1 (invisible)",
            "    @Lcom/example/buck/Anno;() // invisible, parameter 0",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$Inner",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/Anno",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2600",
            "abstract @interface com/example/buck/Anno implements java/lang/annotation/Annotation"
                + " {",
            "",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesRuntimeVisibleAnnotationsOnInnerClassConstructorParameters()
      throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "public class A {",
            "  public class Inner {",
            "    public Inner(@RuntimeRetentionAnno String param) { }",
            "  }",
            "}",
            "@Retention(RetentionPolicy.RUNTIME)",
            "@interface RuntimeRetentionAnno {}")
        .addExpectedStub(
            "com/example/buck/A$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "",
            "  // access flags 0x1",
            "  public <init>(Lcom/example/buck/A;Ljava/lang/String;)V",
            "    // annotable parameter count: 1 (visible)",
            "    @Lcom/example/buck/RuntimeRetentionAnno;() // parameter 0",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$Inner",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/RuntimeRetentionAnno",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2600",
            "abstract @interface com/example/buck/RuntimeRetentionAnno implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesMultipleAnnotationsOnInnerClassConstructorParameters() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "public class A {",
            "  public class Inner {",
            "    public Inner(String param1, @Anno String param2, @RuntimeRetentionAnno String"
                + " param3) { }",
            "  }",
            "}",
            "@interface Anno {}",
            "@Retention(RetentionPolicy.RUNTIME)",
            "@interface RuntimeRetentionAnno {}")
        .addExpectedStub(
            "com/example/buck/A$Inner",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$Inner {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "",
            "  // access flags 0x1",
            "  public"
                + " <init>(Lcom/example/buck/A;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
            "    // annotable parameter count: 3 (visible)",
            "    @Lcom/example/buck/RuntimeRetentionAnno;() // parameter 2",
            "    // annotable parameter count: 3 (invisible)",
            "    @Lcom/example/buck/Anno;() // invisible, parameter 1",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$Inner",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$Inner com/example/buck/A Inner",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/Anno",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2600",
            "abstract @interface com/example/buck/Anno implements java/lang/annotation/Annotation"
                + " {",
            "",
            "}")
        .addExpectedStub(
            "com/example/buck/RuntimeRetentionAnno",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x2600",
            "abstract @interface com/example/buck/RuntimeRetentionAnno implements"
                + " java/lang/annotation/Annotation {",
            "",
            "",
            "  @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void preservesThrowsClausesOnInnerClassConstructors() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public class B {",
            "    B() throws Exception, Throwable {",
            "    }",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$B",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A$B {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x0",
            "  <init>(Lcom/example/buck/A;)V throws java/lang/Exception java/lang/Throwable",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$B",
            "  // access flags 0x1",
            "  public INNERCLASS com/example/buck/A$B com/example/buck/A B",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void abiSafeChangesResultInTheSameOutputJar() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  protected final static int count = 42;",
            "  public String getGreeting() { return \"hello\"; }",
            "  Class<?> clazz;",
            "  public int other;",
            "}")
        .createStubJar()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  protected final static int count = 42;",
            "  public String getGreeting() { return \"merhaba\"; }",
            "  Class<?> clazz = String.class;",
            "  public int other = 32;",
            "}")
        .assertStubJarIsIdentical();
  }

  @Test
  public void shouldIncludeInnerClassTypeParameterReferenceInMethodParameter() throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile(
            "Outer.java",
            "package com.example.buck;",
            "public class Outer {",
            "  public enum Inner { }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.util.Set;",
            "import com.example.buck.Outer;",
            "public interface A {",
            "  void foo(Set<Outer.Inner> test);",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x601",
            "public abstract interface com/example/buck/A {",
            "",
            "  // access flags 0x4019",
            "  public final static enum INNERCLASS com/example/buck/Outer$Inner"
                + " com/example/buck/Outer Inner",
            "",
            "  // access flags 0x401",
            "  // signature (Ljava/util/Set<Lcom/example/buck/Outer$Inner;>;)V",
            "  // declaration: void foo(java.util.Set<com.example.buck.Outer$Inner>)",
            "  public abstract foo(Ljava/util/Set;)V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void ordersChangesResultInADifferentOutputJar() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  protected final static int count = 42;",
            "  public String getGreeting() { return \"hello\"; }",
            "  Class<?> clazz;",
            "  public int other;",
            "}")
        .createStubJar()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  Class<?> clazz;",
            "  public String getGreeting() { return \"hello\"; }",
            "  protected final static int count = 42;",
            "  public int other;",
            "}")
        .assertStubJarIsDifferent();
  }

  @Test
  public void shouldIncludeStaticFields() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public static String foo;",
            "  public final static int count = 42;",
            "  protected static void method() {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x9",
            "  public static Ljava/lang/String; foo",
            "",
            "  // access flags 0x19",
            "  public final static I count = 42",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0xC",
            "  protected static method()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void innerClassesInStubsCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "Outer.java",
            "package com.example.buck;",
            "public class Outer {",
            "  public class Inner {",
            "    public String getGreeting() { return \"hola\"; }",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;", // Note: different package
            "import com.example.buck.Outer;", // Inner class becomes available
            "public class A {",
            "  private Outer.Inner field;", // Reference the inner class
            "}")
        .testCanCompile();
  }

  @Test
  public void stubsWithNestedClassReferencesCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "Dep.java",
            "package com.example.buck;",
            "public class Dep<T> {",
            "  public static class Nested<U> {",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "UsesDepNested.java",
            "package com.example.buck;",
            "public class UsesDepNested {",
            "  public Dep.Nested<Integer> nested;",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public UsesDepNested uses;", // Load the inner class reference first
            "  public Dep.Nested<Integer> nested;", // See if it messes up usage
            "}")
        .testCanCompile();
  }

  @Test
  public void stubsWithNestedEnumReferencesCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "Dep.java",
            "package com.example.buck;",
            "public class Dep {",
            "  public static enum Nested {",
            "    One,",
            "    Two",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "UsesDepNested.java",
            "package com.example.buck;",
            "public class UsesDepNested {",
            "  public Dep.Nested nested;",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public UsesDepNested uses;", // Load the inner class reference first
            "  public Enum nested = Dep.Nested.One;", // See if it messes up usage
            "}")
        .testCanCompile();
  }

  @Test
  public void stubsWithNestedInterfaceReferencesCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "Dep.java",
            "package com.example.buck;",
            "public class Dep<T> {",
            "  public interface Nested<U> {",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "UsesDepNested.java",
            "package com.example.buck;",
            "public class UsesDepNested {",
            "  public Dep.Nested<Integer> nested;",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public UsesDepNested uses;", // Load the inner class reference first
            "  public Dep.Nested<Integer> nested;", // See if it messes up usage
            "}")
        .testCanCompile();
  }

  @Test
  public void stubsWithNestedInterfaceReferencesDetectIncorrectUsage() throws IOException {
    tester
        .setSourceFile(
            "Dep.java",
            "package com.example.buck;",
            "public class Dep<T> {",
            "  public interface Nested<U> {",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "UsesDepNested.java",
            "package com.example.buck;",
            "public class UsesDepNested {",
            "  public Dep.Nested<Integer> nested;",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public UsesDepNested uses;", // Load the inner class reference first
            "  public Dep<String>.Nested<Integer> nested;", // See if it messes up usage
            "}")
        .addExpectedCompileError(
            "A.java:4: error: cannot select a static class from a parameterized type\n"
                + "  public Dep<String>.Nested<Integer> nested;\n"
                + "                    ^")
        .testCanCompile();
  }

  @Test
  public void stubsWithInnerClassReferencesCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "Dep.java",
            "package com.example.buck;",
            "public class Dep<T> {",
            "  public class Inner<U> {",
            "    public class Innerer<V> { }",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "UsesDepInner.java",
            "package com.example.buck;",
            "public class UsesDepInner {",
            "  public Dep<String>.Inner<Integer>.Innerer<Long> inner;",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public UsesDepInner uses;", // Load the inner class reference first
            "  public Dep<String>.Inner<Integer>.Innerer<Long> inner;", // See if it messes up usage
            "}")
        .testCanCompile();
  }

  @Test
  public void stubsWithInnerClassReferencesDetectIncorrectUsage() throws IOException {
    tester
        .setSourceFile(
            "Dep.java",
            "package com.example.buck;",
            "public class Dep<T> {",
            "  public class Inner<U> {",
            "    public class Innerer<V> { }",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "UsesDepInner.java",
            "package com.example.buck;",
            "public class UsesDepInner {",
            "  public Dep<String>.Inner<Integer>.Innerer<Long> inner;",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public UsesDepInner uses;", // Load the inner class reference first
            "  public Dep<String>.Inner.Innerer<Long> inner;", // See if it messes up usage
            "}")
        .addExpectedCompileError(
            "A.java:4: error: improperly formed type, type arguments given on a raw type\n"
                + "  public Dep<String>.Inner.Innerer<Long> inner;\n"
                + "                                  ^")
        .addExpectedCompileError(
            "A.java:4: error: improperly formed type, some parameters are missing\n"
                + "  public Dep<String>.Inner.Innerer<Long> inner;\n"
                + "                    ^")
        .testCanCompile();
  }

  @Test
  public void staticMethodOfInnerClassInStubsCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "Outer.java",
            "package com.example.buck;",
            "public class Outer {",
            "  public static class Inner {",
            "    public static int testStatic() { return 0; }",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;",
            "import com.example.buck.Outer;",
            "public class A {",
            "  public void testMethod() {",
            "    int test = Outer.Inner.testStatic();",
            "  }",
            "}")
        .testCanCompile();
  }

  @Test
  public void privateFieldInInnerClassInStubsCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "Outer.java",
            "package com.example.buck;",
            "public class Outer {",
            "  public String getInnerString() {",
            "    return new Outer.Inner().innerPrivateString;", // Relies on synthetic method
            "  }",
            "  private static final class Inner {",
            "    private final String innerPrivateString = \"hi!\";",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;",
            "import com.example.buck.Outer;",
            "public class A {",
            "  public String testMethod() {",
            "    Outer test = new Outer();",
            "    return test.getInnerString();",
            "  }",
            "}")
        .testCanCompile();
  }

  @Test
  public void nestedClassInStubsCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "Outer.java",
            "package com.example.buck;",
            "public class Outer {",
            "  public class Inner {",
            "    public class Nested { }",
            "  }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;",
            "import com.example.buck.Outer;",
            "public class A {",
            "  private Outer.Inner.Nested field;",
            "}")
        .testCanCompile();
  }

  @Test
  public void methodReturningPrivateInnerClassInStubsCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "Test.java",
            "package com.example.buck;",
            "public class Test {",
            "  public static PrivateInner getPrivateInner() {",
            "    return new PrivateInner();",
            "  }",
            "  private static class PrivateInner { }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;",
            "import com.example.buck.Test;",
            "public class A {",
            "  public void test() {",
            "    Object privateInnerObject = Test.getPrivateInner();",
            "  }",
            "}")
        .testCanCompile();
  }

  @Test
  public void methodOfPrivateSuperClassOfInnerPublicClassInStubsCanBeCompiledAgainst()
      throws IOException {
    tester
        .setSourceFile(
            "Test.java",
            "package com.example.buck;",
            "public class Test {",
            "  private class PrivateInner { ",
            "   public void foo() { }",
            "  }",
            "  public class PublicInner extends PrivateInner { }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;",
            "import com.example.buck.Test;",
            "public class A {",
            "  public void test() {",
            "    Test test = new Test();",
            "    Test.PublicInner inner = test.new PublicInner();",
            "    inner.foo();",
            "  }",
            "}")
        .testCanCompile();
  }

  @Test
  public void bridgeMethodInStubsCanBeCompiledAgainst() throws IOException {
    notYetImplementedForMissingClasspath();

    tester
        .setSourceFile(
            "TestGenericInterface.java",
            "package com.example.buck;",
            "public interface TestGenericInterface<T> {",
            "  public int compare(T a, T b);",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "TestComparator.java",
            "package com.example.buck;",
            "import com.example.buck.TestGenericInterface;",
            "public class TestComparator implements TestGenericInterface<Integer> {",
            "  public int compare(Integer a, Integer b) { return 1; }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;",
            "import com.example.buck.TestComparator;",
            "import com.example.buck.TestGenericInterface;",
            "public class A {",
            "  public void test() {",
            "    Object first = 1;",
            "    Object second = 2;",
            "    TestGenericInterface com = new TestComparator();",
            "    int result = com.compare(first, second);", // Relies on bridge method
            "  }",
            "}")
        .testCanCompile();
  }

  @Test
  public void enumInStubsCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "TestEnum.java",
            "package com.example.buck;",
            "public enum TestEnum {",
            "  Value1,",
            "  Value2",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;",
            "import com.example.buck.TestEnum;",
            "public class A {",
            "  TestEnum testEnum = TestEnum.Value1;",
            "}")
        .testCanCompile();
  }

  @Test
  public void genericClassInStubsCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "GenericClass.java",
            "package com.example.buck;",
            "public class GenericClass<T> {",
            "  public T theField;",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;",
            "import com.example.buck.GenericClass;",
            "public class A {",
            "  public void test() {",
            "    GenericClass<String> field = new GenericClass<>();",
            "    field.theField = \"facebook\";",
            "  }",
            "}")
        .testCanCompile();
  }

  @Test
  public void classExtendingGenericClassInStubsCanBeCompiledAgainst() throws IOException {
    tester
        .setSourceFile(
            "GenericClass.java",
            "package com.example.buck;",
            "public class GenericClass<T> {",
            "  public T theField;",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;",
            "import com.example.buck.GenericClass;",
            "public class A<T, M> extends GenericClass<T> {",
            "  public M otherField;",
            "  public A(T t, M m) {",
            "    this.theField = t;",
            "    this.otherField = m;",
            "  }",
            "}")
        .testCanCompile();
  }

  @Test
  public void privateConstructorResultsInCorrectCompileError() throws IOException {
    tester
        .setSourceFile(
            "PrivateTest.java",
            "package com.example.buck;",
            "public class PrivateTest {",
            "  private PrivateTest() { }",
            "}")
        .addExpectedStub(
            "com/example/buck/PrivateTest",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/PrivateTest {",
            "",
            "",
            "  // access flags 0x2",
            "  private <init>()V",
            "}")
        .createAndCheckStubJar()
        .addStubJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck2;",
            "import com.example.buck.PrivateTest;",
            "public class A {",
            "  public void foo() {",
            "   PrivateTest test = new PrivateTest();",
            "  }",
            "}")
        .addExpectedCompileError(
            Joiner.on('\n')
                .join(
                    "A.java:5: error: PrivateTest() has private access in"
                        + " com.example.buck.PrivateTest",
                    "   PrivateTest test = new PrivateTest();",
                    "                      ^"))
        .testCanCompile();
  }

  @Test
  public void shouldPreserveSynchronizedKeywordOnMethods() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public synchronized void doMagic() {}",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x21",
            "  public synchronized doMagic()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldKeepMultipleFieldsWithSameDescValue() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public static final A SEVERE = new A();",
            "  public static final A NOT_SEVERE = new A();",
            "  public static final A QUITE_MILD = new A();",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x19",
            "  public final static Lcom/example/buck/A; SEVERE",
            "",
            "  // access flags 0x19",
            "  public final static Lcom/example/buck/A; NOT_SEVERE",
            "",
            "  // access flags 0x19",
            "  public final static Lcom/example/buck/A; QUITE_MILD",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldNotStubClinit() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public static int i = 3;",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x9",
            "  public static I i",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x8",
            "  static <clinit>()V", // Should not stub this, even though it's default access
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x9",
            "  public static I i",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void stubJarIsEquallyAtHomeWalkingADirectoryOfClassFiles() throws IOException {
    AbsPath fullJarPath =
        compileToJar(
            EMPTY_CLASSPATH,
            Collections.emptyList(),
            Collections.emptyList(),
            null,
            "A.java",
            Joiner.on("\n")
                .join(
                    ImmutableList.of(
                        "package com.example.buck;",
                        "public class A {",
                        "  public String toString() { return null; }",
                        "  public void eatCake() {}",
                        "}")),
            temp.newFolder());

    AbsPath classDir = AbsPath.of(temp.newFolder().toPath());

    Unzip.extractArchive(
        AbsPath.of(temp.getRoot().toPath()), fullJarPath.getPath(), classDir.getPath());

    AbsPath stubJarPath = createStubJar(classDir);
    tester
        .setStubJar(stubJarPath)
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public toString()Ljava/lang/String;",
            "",
            "  // access flags 0x1",
            "  public eatCake()V",
            "}")
        .checkStubJar();
  }

  @Test
  public void shouldPreserveManifestEntries() throws IOException {
    DeterministicManifest manifest = new DeterministicManifest();
    manifest.getMainAttributes().putValue("Test-Value", "Test");
    manifest.setEntryAttribute("com/example/buck/A.class", "Test-Value", "Test");

    DeterministicManifest expectedManifest = new DeterministicManifest(manifest);
    expectedManifest.setManifestAttribute("Manifest-Version", "1.0");
    expectedManifest.setEntryAttribute(
        "com/example/buck/A.class",
        "Murmur3-128-Digest",
        Integer.parseInt(sourceVersion) >= 11
            ? "a52865fe9a9d028824cc5f50b3bc2f77"
            : "6ddaf37b8a78b7ae705f31d1512c13c6");

    tester
        .setSourceFile("A.java", "package com.example.buck;", "public class A { }")
        .setManifest(manifest)
        .setExpectedStubManifest(expectedManifest)
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldNotIncludeSyntheticFields() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public void method() {",
            "    assert false;",
            "  }",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1018",
            "  final static synthetic Z $assertionsDisabled", // Should remove this field
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public method()V",
            "",
            "  // access flags 0x8",
            "  static <clinit>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public method()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldNotIncludeSyntheticClasses() throws IOException {
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  enum E { Value };",
            "  public void method(E e) {",
            "    switch (e) {",
            "      case Value: break;",
            "    }",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A$E",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4030",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A$E;>;",
            "// declaration: com/example/buck/A$E extends java.lang.Enum<com.example.buck.A$E>",
            "final enum com/example/buck/A$E extends java/lang/Enum {",
            "",
            "JDK11:  NESTHOST com/example/buck/A",
            "  // access flags 0x4018",
            "  final static enum INNERCLASS com/example/buck/A$E com/example/buck/A E",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A$E; Value",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A$E;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$E;",
            "",
            "  // access flags 0x2",
            "  private <init>(Ljava/lang/String;I)V",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$E",
            // Should not include this class
            "  // access flags 0x4018",
            "  final static enum INNERCLASS com/example/buck/A$E com/example/buck/A E",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public method(Lcom/example/buck/A$E;)V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$E",
            "  // access flags 0x4018",
            "  final static enum INNERCLASS com/example/buck/A$E com/example/buck/A E",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public method(Lcom/example/buck/A$E;)V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldIncludePackageInfoClassIfAnnotated() throws IOException {
    tester
        .setSourceFile("package-info.java", "@Deprecated", "package com.example.buck;")
        .addExpectedStub(
            "com/example/buck/package-info",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x1600",
            "abstract synthetic interface com/example/buck/package-info {",
            "",
            "",
            "  @Ljava/lang/Deprecated;()",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldNotIncludePackageInfoClassIfNotAnnotated() throws IOException {
    tester.setSourceFile("package-info.java", "package com.example.buck;").createAndCheckStubJar();
  }

  @Test
  public void shouldIncludeInnerClassReferencesInPackageInfoClass() throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A {",
            "  public @interface Anno { }",
            "}")
        .createStubJar()
        .addStubJarToClasspath()
        .setSourceFile("package-info.java", "@A.Anno", "package com.example.buck;")
        .addExpectedStub(
            "com/example/buck/package-info",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x1600",
            "abstract synthetic interface com/example/buck/package-info {",
            "",
            "",
            "  @Lcom/example/buck/A$Anno;() // invisible",
            "  // access flags 0x2609",
            "  public static abstract INNERCLASS com/example/buck/A$Anno com/example/buck/A Anno",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldNotIncludeSyntheticMethods() throws IOException {
    tester
        .setSourceFile(
            "A.java", "package com.example.buck;", "public enum A {", "  Value1 { }", "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4021",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;",
            "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>",
            "public enum com/example/buck/A extends java/lang/Enum {",
            "",
            "JDK11:  NESTMEMBER com/example/buck/A$1",
            "  // access flags 0x4010",
            "  final enum INNERCLASS com/example/buck/A$1 null null",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A; Value1",
            "",
            "  // access flags 0x101A",
            "  private final static synthetic [Lcom/example/buck/A; $VALUES",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
            "",
            "  // access flags 0x2",
            "  // signature ()V",
            "  // declaration: void <init>()",
            "  private <init>(Ljava/lang/String;I)V",
            "",
            "  // access flags 0x100A",
            "  private static synthetic $values()[Lcom/example/buck/A;",
            "JDK8:",
            "JDK8:  // access flags 0x1000",
            "JDK8:  synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V",
            // Should not include this method
            "",
            "  // access flags 0x8",
            "  static <clinit>()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x4021",
            "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;",
            "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>",
            "public enum com/example/buck/A extends java/lang/Enum {",
            "",
            "",
            "  // access flags 0x4019",
            "  public final static enum Lcom/example/buck/A; Value1",
            "",
            "  // access flags 0x9",
            "  public static values()[Lcom/example/buck/A;",
            "",
            "  // access flags 0x9",
            "  public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
            "",
            "  // access flags 0x2",
            "  private <init>(Ljava/lang/String;I)V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldIncludeGenericBridgeMethods() throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A implements Comparable<A> {",
            "  public int compareTo(A other) {",
            "    return 0;",
            "  }",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "// signature Ljava/lang/Object;Ljava/lang/Comparable<Lcom/example/buck/A;>;",
            "// declaration: com/example/buck/A implements"
                + " java.lang.Comparable<com.example.buck.A>",
            "public class com/example/buck/A implements java/lang/Comparable {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public compareTo(Lcom/example/buck/A;)I",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge compareTo(Ljava/lang/Object;)I",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "// signature Ljava/lang/Object;Ljava/lang/Comparable<Lcom/example/buck/A;>;",
            "// declaration: com/example/buck/A implements"
                + " java.lang.Comparable<com.example.buck.A>",
            "public class com/example/buck/A implements java/lang/Comparable {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public compareTo(Lcom/example/buck/A;)I",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge compareTo(Ljava/lang/Object;)I",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldIncludeGenericOverrideBridgeMethods() throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A extends Super<String> {",
            "  @Override",
            "  public int compareTo(String s) {",
            "    return 0;",
            "  }",
            "}",
            "class Super<T extends CharSequence> implements Comparable<T> {",
            "  @Override",
            "  public int compareTo(T t) {",
            "    return 0;",
            "  }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "// signature Lcom/example/buck/Super<Ljava/lang/String;>;",
            "// declaration: com/example/buck/A extends com.example.buck.Super<java.lang.String>",
            "public class com/example/buck/A extends com/example/buck/Super {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public compareTo(Ljava/lang/String;)I",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge compareTo(Ljava/lang/CharSequence;)I",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge compareTo(Ljava/lang/Object;)I",
            "}")
        .addExpectedStub(
            "com/example/buck/Super",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x20",
            "// signature"
                + " <T::Ljava/lang/CharSequence;>Ljava/lang/Object;Ljava/lang/Comparable<TT;>;",
            "// declaration: com/example/buck/Super<T extends java.lang.CharSequence> implements"
                + " java.lang.Comparable<T>",
            "class com/example/buck/Super implements java/lang/Comparable {",
            "",
            "",
            "  // access flags 0x0",
            "  <init>()V",
            "",
            "  // access flags 0x1",
            "  // signature (TT;)I",
            "  // declaration: int compareTo(T)",
            "  public compareTo(Ljava/lang/CharSequence;)I",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge compareTo(Ljava/lang/Object;)I",
            "}")
        .createAndCheckStubJar();
  }

  public class Foo<T> implements Callable<T> {

    @Override
    public T call() {
      return null;
    }
  }

  @Test
  public void shouldIncludeCovariantReturnBridgeMethods() throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile(
            "Super.java",
            "package com.example.buck;",
            "public class Super<T, U> {",
            "  protected Super getThis() { return this; }",
            "  public java.util.List<T> getList() { return null; }",
            "}")
        .compileFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A extends Super<String, String> {",
            "  protected A getThis() { return this; }",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "// signature Lcom/example/buck/Super<Ljava/lang/String;Ljava/lang/String;>;",
            "// declaration: com/example/buck/A extends com.example.buck.Super<java.lang.String,"
                + " java.lang.String>",
            "public class com/example/buck/A extends com/example/buck/Super {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x4",
            "  protected getThis()Lcom/example/buck/A;",
            "",
            "  // access flags 0x1044",
            "  protected synthetic bridge getThis()Lcom/example/buck/Super;",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "// signature Lcom/example/buck/Super<Ljava/lang/String;Ljava/lang/String;>;",
            "// declaration: com/example/buck/A extends com.example.buck.Super<java.lang.String,"
                + " java.lang.String>",
            "public class com/example/buck/A extends com/example/buck/Super {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x4",
            "  protected getThis()Lcom/example/buck/A;",
            "",
            "  // access flags 0x1044",
            "  protected synthetic bridge getThis()Lcom/example/buck/Super;",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void
      shouldCopyAccessibilityAnnotationsAndParamNamesFromOverriderAndThrowsFromOverriddenToBridgeMethods()
          throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile("Anno.java", "package com.example.buck;", "public @interface Anno { }")
        .compileFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "Super.java",
            "package com.example.buck;",
            "public class Super {",
            "  protected @SuperAnno Super getThis(@SuperAnno int param) throws Exception { return"
                + " this; }",
            "  protected Super varargsMethod(int... args) { return this; };",
            "}",
            "@interface SuperAnno { }")
        .compileFullJar()
        .addFullJarToClasspath()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "import java.io.IOException;",
            "public class A extends Super {",
            "  public @Anno A getThis(@Anno final int i) throws IOException { return this; }",
            "  public A varargsMethod(int... args) { return this; };",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A extends com/example/buck/Super {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public getThis(I)Lcom/example/buck/A; throws java/io/IOException",
            "  @Lcom/example/buck/Anno;() // invisible",
            "    // annotable parameter count: 1 (invisible)",
            "    @Lcom/example/buck/Anno;() // invisible, parameter 0",
            "",
            "  // access flags 0x81",
            "  public varargs varargsMethod([I)Lcom/example/buck/A;",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge varargsMethod([I)Lcom/example/buck/Super;",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge getThis(I)Lcom/example/buck/Super; throws"
                + " java/lang/Exception",
            "  @Lcom/example/buck/Anno;() // invisible",
            "    // annotable parameter count: 1 (invisible)",
            "    @Lcom/example/buck/Anno;() // invisible, parameter 0",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A extends com/example/buck/Super {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public getThis(I)Lcom/example/buck/A; throws java/io/IOException",
            "  @Lcom/example/buck/Anno;() // invisible",
            "    // annotable parameter count: 1 (invisible)",
            "    @Lcom/example/buck/Anno;() // invisible, parameter 0",
            "",
            "  // access flags 0x81",
            "  public varargs varargsMethod([I)Lcom/example/buck/A;",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge varargsMethod([I)Lcom/example/buck/Super;",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge getThis(I)Lcom/example/buck/Super; throws"
                + " java/lang/Exception",
            "  @Lcom/example/buck/Anno;() // invisible",
            "    // annotable parameter count: 1 (invisible)",
            "    @Lcom/example/buck/Anno;() // invisible, parameter 0",
            "}")
        .createAndCheckStubJar();
  }

  /**
   * There's this fun case in javac where if a public class has a non-public superclass, all public
   * methods on the superclass get bridges in the subclass. Let's make sure
   */
  @Test
  public void shouldIncludeNonPublicBaseClassBridgeMethods() throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile(
            "B.java",
            "package com.example.buck;",
            "class B {",
            "  public void foo() {}",
            "public void bar() {}",
            "}")
        .compileFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "Super.java",
            "package com.example.buck;",
            "abstract class Super extends B {",
            "  @Override",
            "  public void bar() {}",
            "  public void baz() {}",
            "  public abstract void abstractMethod();", // This one won't get bridged
            "  public final void finalMethod() { }", // Nor will this one
            "}")
        .compileFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java", "package com.example.buck;", "public abstract class A extends Super {", "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x421",
            "public abstract class com/example/buck/A extends com/example/buck/Super {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge baz()V",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge bar()V",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge foo()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x421",
            "public abstract class com/example/buck/A extends com/example/buck/Super {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge baz()V",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge bar()V",
            "",
            "  // access flags 0x1041",
            "  public synthetic bridge foo()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void shouldNotIncludeNonPublicBaseClassBridgeMethodsWhenManuallyDone() throws IOException {
    notYetImplementedForMissingClasspath();
    tester
        .setSourceFile(
            "B.java",
            "package com.example.buck;",
            "class B {",
            "  public void foo() {}",
            "  public void bar() {}",
            "}")
        .compileFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "Super.java",
            "package com.example.buck;",
            "public class Super extends B {",
            "  public void bar() {}",
            "}")
        .compileFullJar()
        .addFullJarToClasspathAlways()
        .setSourceFile(
            "A.java",
            "package com.example.buck;",
            "public class A extends Super {",
            "  public void foo() {}",
            "}")
        .addExpectedFullAbi(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A extends com/example/buck/Super {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public foo()V",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "JDK8:// class version 52.0 (52)",
            "JDK11:// class version 55.0 (55)",
            "// access flags 0x21",
            "public class com/example/buck/A extends com/example/buck/Super {",
            "",
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // access flags 0x1",
            "  public foo()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void kotlinObjectWithJvmStatic() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      // TODO T53836707 the methods in the synthetic class are wrongly stripped, preventing
      //  compilation
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u000c\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0006\\u0008\\u00c6\\u0002\\u0018\\u00002\\u00020\\u0001B\\u0009\\u0008\\u0002\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003R\\u001c\\u0010\\u0004\\u001a\\u00020\\u00018\\u0006X\\u0087\\u0004\\u00a2\\u0006\\u000e\\n"
            + "\\u0000\\u0012\\u0004\\u0008\\u0005\\u0010\\u0003\\u001a\\u0004\\u0008\\u0006\\u0010\\u0007\"},"
            + " d2={\"Lcom/example/buck/Obj;\", \"\", \"<init>\", \"()V\", \"prop\","
            + " \"getProp$annotations\", \"getProp\", \"()Ljava/lang/Object;\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u000c\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0006\\u0008\\u00c6\\u0002\\u0018\\u00002\\u00020\\u0001B\\u0009\\u0008\\u0002\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003R\\u001c\\u0010\\u0004\\u001a\\u00020\\u00018\\u0006X\\u0087\\u0004\\u00a2\\u0006\\u000e\\n"
            + "\\u0000\\u0012\\u0004\\u0008\\u0005\\u0010\\u0003\\u001a\\u0004\\u0008\\u0006\\u0010\\u0007\"},"
            + " d2={\"Lcom/example/buck/Obj;\", \"\", \"<init>\", \"()V\", \"prop\","
            + " \"getProp$annotations\", \"getProp\", \"()Ljava/lang/Object;\"})";
    tester
        .setSourceFile(
            "Obj.kt",
            "package com.example.buck",
            "object Obj {",
            "  @JvmStatic val prop: Any = Any()",
            "}")
        .addExpectedStub(
            "com/example/buck/Obj",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "public final class com/example/buck/Obj {",
            "",
            "  // compiled from: Obj.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x19",
            "  public final static Lcom/example/buck/Obj; INSTANCE",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "",
            "  // access flags 0x19",
            "  public final static getProp()Ljava/lang/Object;",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "",
            "  // DEPRECATED",
            "  // access flags 0x21009",
            "  public static synthetic getProp$annotations()V",
            "  @Lkotlin/jvm/JvmStatic;()",
            "    RETURN",
            "    MAXSTACK = 0",
            "    MAXLOCALS = 0",
            "",
            "  // access flags 0x2",
            "  private <init>()V",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void kotlinClassWithDeprecatedMethods() throws IOException {
    if (!isValidForKotlin()) {
      return;
    }

    if (testingMode.equals(MODE_SOURCE_BASED)) {
      return;
    }

    tester = new Tester(Language.KOTLIN);
    String metadata21 =
        "  @Lkotlin/Metadata;(mv={2, 1, 0}, k=1, xi=48, d1={\"\\u0000\\u0018\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0002\\u0008\\u0002\\n"
            + "\\u0002\\u0010\\u000b\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0012\\u0010\\u0004\\u001a\\u00020\\u00052\\u0008\\u0008\\u0002\\u0010\\u0006\\u001a\\u00020\\u0005H\\u0007J\\u001a\\u0010\\u0004\\u001a\\u00020\\u00052\\u0008\\u0008\\u0002\\u0010\\u0006\\u001a\\u00020\\u00052\\u0008\\u0008\\u0002\\u0010\\u0007\\u001a\\u00020\\u0008\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"testFunc\", \"\","
            + " \"str\", \"b\", \"\"})";
    String metadata20 =
        "  @Lkotlin/Metadata;(mv={2, 0, 0}, k=1, xi=48, d1={\"\\u0000\\u0018\\n"
            + "\\u0002\\u0018\\u0002\\n"
            + "\\u0002\\u0010\\u0000\\n"
            + "\\u0002\\u0008\\u0003\\n"
            + "\\u0002\\u0010\\u000e\\n"
            + "\\u0002\\u0008\\u0002\\n"
            + "\\u0002\\u0010\\u000b\\u0018\\u00002\\u00020\\u0001B\\u0007\\u00a2\\u0006\\u0004\\u0008\\u0002\\u0010\\u0003J\\u0012\\u0010\\u0004\\u001a\\u00020\\u00052\\u0008\\u0008\\u0002\\u0010\\u0006\\u001a\\u00020\\u0005H\\u0007J\\u001a\\u0010\\u0004\\u001a\\u00020\\u00052\\u0008\\u0008\\u0002\\u0010\\u0006\\u001a\\u00020\\u00052\\u0008\\u0008\\u0002\\u0010\\u0007\\u001a\\u00020\\u0008\"},"
            + " d2={\"Lcom/example/buck/A;\", \"\", \"<init>\", \"()V\", \"testFunc\", \"\","
            + " \"str\", \"b\", \"\"})";
    tester
        .setSourceFile(
            "A.kt",
            "package com.example.buck",
            "class A {",
            "  @Deprecated(\"Testing deprecated\", level=DeprecationLevel.HIDDEN)",
            "  fun testFunc(str: String = \"default\"): String { return str }",
            "  fun testFunc(str: String = \"default\", b: Boolean = true): String { return if (b)"
                + " str else \"\" }",
            "}")
        .addExpectedStub(
            "com/example/buck/A",
            "// class version 52.0 (52)",
            "// access flags 0x31",
            "public final class com/example/buck/A {",
            "",
            "  // compiled from: A.kt",
            "",
            isKotlin21() ? metadata21 : metadata20,
            "",
            "  // access flags 0x1",
            "  public <init>()V",
            "",
            "  // DEPRECATED",
            "  // access flags 0x21011",
            "  public final synthetic testFunc(Ljava/lang/String;)Ljava/lang/String;",
            "  @Lkotlin/Deprecated;(message=\"Testing deprecated\","
                + " level=Lkotlin/DeprecationLevel;.HIDDEN)",
            "",
            "  // DEPRECATED",
            "  // access flags 0x21009",
            "  public static synthetic"
                + " testFunc$default(Lcom/example/buck/A;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String;",
            "",
            "  // access flags 0x11",
            "  public final testFunc(Ljava/lang/String;Z)Ljava/lang/String;",
            "  @Lorg/jetbrains/annotations/NotNull;() // invisible",
            "    // annotable parameter count: 2 (invisible)",
            "    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0",
            "}")
        .createAndCheckStubJar();
  }

  @Test
  public void doNotStripClassSuffixIfItDoesNotExist() {
    assertEquals("A.aut", StubJar.pathWithoutClassSuffix(Paths.get("A.aut")));
  }

  @Test
  public void stripClassSuffixIfItExists() {
    assertEquals("A", StubJar.pathWithoutClassSuffix(Paths.get("A.class")));
  }

  private AbsPath createStubJar(AbsPath fullJar) throws IOException {
    AbsPath stubJar = fullJar.getParent().resolve("stub.jar");
    new StubJar(fullJar, false).setCompatibilityMode(AbiGenerationMode.SOURCE).writeTo(stubJar);
    return stubJar;
  }

  private AbsPath compileToJar(
      SortedSet<Path> classpath,
      List<Processor> processors,
      List<String> additionalOptions,
      DeterministicManifest manifest,
      String fileName,
      String source,
      File outputDir)
      throws IOException {
    try (TestCompiler compiler = new TestCompiler()) {
      compiler.init();
      compiler.addCompilerOptions(additionalOptions);
      if (manifest != null) {
        compiler.setManifest(manifest);
      }
      compiler.addSourceFileContents(fileName, source);
      compiler.addClasspath(classpath);
      compiler.setSourceLevel(sourceVersion);
      compiler.setTargetLevel(sourceVersion);
      compiler.setProcessors(processors);

      compiler.compile();

      AbsPath jarPath = AbsPath.of(outputDir.toPath()).resolve("output.jar");
      compiler.getClasses().createJar(jarPath.getPath(), false);
      return jarPath;
    }
  }

  private AbsPath compileToKotlinJar(
      SortedSet<Path> classpath, String fileName, String source, File outputDir)
      throws IOException {
    try (KotlinTestCompiler compiler = new KotlinTestCompiler()) {
      compiler.init();
      compiler.addSourceFileContents(fileName, source);
      compiler.addClasspath(classpath);

      compiler.compile();

      AbsPath jarPath = AbsPath.of(outputDir.toPath()).resolve("output.jar");
      compiler.getClasses().createJar(jarPath.getPath(), false);
      return jarPath;
    }
  }

  private AbsPath compileToKotlinAbiJar(
      SortedSet<Path> classpath, String fileName, String source, File outputDir)
      throws IOException {
    try (KotlinTestCompiler compiler = new KotlinTestCompiler()) {
      compiler.init();
      compiler.addSourceFileContents(fileName, source);
      compiler.addClasspath(classpath);

      compiler.compile();

      AbsPath jarPath = AbsPath.of(outputDir.toPath()).resolve("output.jar");
      compiler.getAbiClasses().createJar(jarPath.getPath(), false);
      return jarPath;
    }
  }

  private Tester createAnnotationFullJar() throws IOException {
    return tester
        .setSourceFile(
            "Foo.java",
            "package com.example.buck;",
            "import java.lang.annotation.*;",
            "import static java.lang.annotation.ElementType.*;",
            "@Retention(RetentionPolicy.RUNTIME)",
            "@Target(value={CONSTRUCTOR, FIELD, METHOD, PARAMETER, TYPE})",
            "public @interface Foo {",
            "  int primitiveValue() default 0;",
            "  String stringValue() default \"default\";",
            "  String[] stringArrayValue() default {\"Hello\"};",
            "  Retention annotationValue() default @Retention(RetentionPolicy.SOURCE);",
            "  Retention[] annotationArrayValue() default {};",
            "  RetentionPolicy enumValue () default RetentionPolicy.CLASS;",
            "  Class typeValue() default Foo.class;",
            "  Class<? extends Runnable>[] runnableValues() default {};",
            "  @Target({TYPE_PARAMETER, TYPE_USE})",
            "  @interface TypeAnnotation { }",
            "}")
        .compileFullJar();
  }

  private void notYetImplementedForMissingClasspath() {
    assumeThat(testingMode, not(equalTo(MODE_SOURCE_BASED_MISSING_DEPS)));
  }

  private void notYetImplementedForSource() {
    assumeThat(testingMode, equalTo(MODE_JAR_BASED));
  }

  private boolean isValidForKotlin() {
    // System.getProperty("java.class.path") returning classpath with ":" as separator which means
    // that KotlinTestCompiler crashes (separator should be ";" on Windows)
    assumeThat(Platform.detect(), not(Platform.WINDOWS));
    return !testingMode.equals(MODE_SOURCE_BASED_MISSING_DEPS);
  }

  enum Language {
    JAVA,
    KOTLIN,
  }

  private final class Tester {

    private final List<String> expectedStubDirectory = new ArrayList<>();
    private final List<String> actualStubDirectory = new ArrayList<>();
    private final List<String> actualFullDirectory = new ArrayList<>();
    private final Map<String, List<String>> expectedFullAbis = new HashMap<>();
    private final Map<String, List<String>> actualFullAbis = new HashMap<>();
    private final Map<String, List<String>> expectedStubs = new HashMap<>();
    private final Map<String, List<String>> actualStubs = new HashMap<>();
    private final List<String> expectedCompileErrors = new ArrayList<>();
    private final List<String> additionalOptions = new ArrayList<>();
    private Language language;
    private DeterministicManifest manifest;
    private List<String> expectedStubManifest;
    private List<String> actualStubManifest;
    private String sourceFileName = "";
    private String sourceFileContents = "";
    private ImmutableSortedSet<Path> universalClasspath = EMPTY_CLASSPATH;
    private ImmutableSortedSet<Path> classpath = EMPTY_CLASSPATH;
    private AbsPath stubJarPath;
    private AbsPath fullJarPath;
    private boolean issueAPWarnings;
    private final Pattern javaVersionSpecificPattern = Pattern.compile("^JDK(\\d+):.*");

    public Tester(Language language) {
      this.language = language;

      expectedStubDirectory.add("META-INF/");

      if (language.equals(Language.KOTLIN)) {
        expectedStubDirectory.add("META-INF/main.kotlin_module");
      }
    }

    private void resetActuals() {
      actualStubDirectory.clear();
      actualFullDirectory.clear();
      actualFullAbis.clear();
      actualStubs.clear();
      stubJarPath = null;
      fullJarPath = null;
    }

    public Tester setManifest(DeterministicManifest manifest) {
      this.manifest = manifest;
      return this;
    }

    public Tester setExpectedStubManifest(DeterministicManifest manifest) throws IOException {
      try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
        manifest.write(out);
        try (InputStreamReader reader =
            new InputStreamReader(new ByteArrayInputStream(out.toByteArray()))) {
          expectedStubManifest = CharStreams.readLines(reader);
        }
      }
      return this;
    }

    public Tester setSourceFile(String fileName, String... lines) {
      sourceFileName = fileName;
      sourceFileContents = Joiner.on('\n').join(lines);
      return this;
    }

    public Tester addCompilerOptions(String... options) {
      additionalOptions.addAll(Arrays.asList(options));
      return this;
    }

    public Tester addExpectedFullAbi(String classBinaryName, String... abiLines) {
      String filePath = classBinaryName + ".class";
      expectedFullAbis.put(filePath, filterLinesForCurrentJavaVersion(abiLines));
      return this;
    }

    public Tester addExpectedStub(String classBinaryName, String... stubLines) {
      String filePath = classBinaryName + ".class";
      if (!expectedStubDirectory.contains("com/")) {
        expectedStubDirectory.add("com/");
        expectedStubDirectory.add("com/example/");
        expectedStubDirectory.add("com/example/buck/");
      }

      expectedStubDirectory.add(filePath);
      expectedStubs.put(filePath, filterLinesForCurrentJavaVersion(stubLines));
      return this;
    }

    private List<String> filterLinesForCurrentJavaVersion(String[] lines) {
      List<String> result = new ArrayList<>();
      for (String line : lines) {
        Matcher m = javaVersionSpecificPattern.matcher(line);
        if (m.matches()) {
          if (m.group(1).equals(sourceVersion)) {
            String s = line.substring(line.indexOf(':') + 1);
            result.add(s);
          }
        } else {
          result.add(line);
        }
      }
      return result;
    }

    public Tester addExpectedCompileError(String... compileErrorLines) {
      expectedCompileErrors.add(
          String.join("\n", filterLinesForCurrentJavaVersion(compileErrorLines)));
      return this;
    }

    public Tester setStubJar(AbsPath stubJarPath) {
      this.stubJarPath = stubJarPath;
      return this;
    }

    public Tester setIssueAnnotationProcessorWarnings(boolean value) {
      this.issueAPWarnings = value;
      return this;
    }

    public Tester createAndCheckStubJar() throws IOException {
      if (!expectedFullAbis.isEmpty()) {
        compileFullJar();
        dumpFullJarAbi();

        for (String entryName : expectedStubDirectory) {
          if (!expectedFullAbis.containsKey(entryName)) {
            // You don't have to put expectations for all full ABIs, only those that you feel really
            // need to be a certain way for the test to be valid.
            continue;
          }
          assertEquals(
              "Full ABI for " + entryName + " is not what was expected.",
              Joiner.on('\n').join(expectedFullAbis.get(entryName)),
              Joiner.on('\n').join(actualFullAbis.get(entryName)));
        }
      }

      createStubJar();
      return checkStubJar();
    }

    public Tester checkStubJar() throws IOException {
      dumpStubJar();

      assertEquals(
          "File list is not correct.",
          expectedStubDirectory.stream().sorted().collect(Collectors.toList()),
          actualStubDirectory.stream().sorted().collect(Collectors.toList()));

      for (String entryName : expectedStubDirectory) {
        if (entryName.endsWith("/") || entryName.endsWith(".kotlin_module")) {
          continue;
        }

        List<String> expectedStub = expectedStubs.get(entryName);
        if (expectedStub.isEmpty()) {
          // Exists in the JAR, but we don't care about its contents.
          continue;
        }

        assertEquals(
            "Stub for " + entryName + " is not correct",
            Joiner.on('\n').join(expectedStubs.get(entryName)),
            Joiner.on('\n').join(actualStubs.get(entryName)));
      }

      if (expectedStubManifest != null) {
        assertEquals(
            Joiner.on('\n').join(expectedStubManifest), Joiner.on('\n').join(actualStubManifest));
      }

      return this;
    }

    public Tester setLanguage(Language language) {
      this.language = language;
      return this;
    }

    public Tester createStubJar() throws IOException {
      File outputDir = temp.newFolder();
      if (language.equals(Language.KOTLIN) && testingMode.equals(MODE_SOURCE_BASED)) {
        stubJarPath =
            compileToKotlinAbiJar(classpath, sourceFileName, sourceFileContents, temp.newFolder());
      } else if (!testingMode.equals(MODE_JAR_BASED)) {
        SortedSet<Path> classpath1 =
            testingMode.equals(MODE_SOURCE_BASED)
                ? ImmutableSortedSet.<Path>naturalOrder()
                    .addAll(universalClasspath)
                    .addAll(classpath)
                    .build()
                : universalClasspath;
        AbsPath stubJar = AbsPath.of(outputDir.toPath()).resolve("stub.jar");
        JarBuilder jarBuilder = new JarBuilder();

        try (TestCompiler testCompiler = new TestCompiler()) {
          testCompiler.init();
          testCompiler.addCompilerOptions(additionalOptions);
          if (manifest != null) {
            testCompiler.setManifest(manifest);
          }
          if (testingMode.equals(MODE_SOURCE_BASED_MISSING_DEPS)) {
            testCompiler.useFrontendOnlyJavacTask();
          }
          testCompiler.addSourceFileContents(sourceFileName, sourceFileContents);
          testCompiler.addClasspath(classpath1);
          testCompiler.setSourceLevel(sourceVersion);
          testCompiler.setTargetLevel(sourceVersion);
          testCompiler.setProcessors(
              Collections.singletonList(
                  new AbstractProcessor() {
                    @Override
                    public Set<String> getSupportedAnnotationTypes() {
                      return Collections.singleton("*");
                    }

                    @Override
                    public SourceVersion getSupportedSourceVersion() {
                      return SourceVersion.RELEASE_8;
                    }

                    @Override
                    public Set<String> getSupportedOptions() {
                      return Collections.emptySet();
                    }

                    @Override
                    public boolean process(
                        Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
                      if (issueAPWarnings) {
                        processingEnv.getMessager().printMessage(Kind.WARNING, "Warning");
                      }
                      return false;
                    }
                  }));
          StubGenerator generator =
              new StubGenerator(
                  SourceVersionUtils.getSourceVersionFromTarget(sourceVersion),
                  testCompiler.getElements(),
                  testCompiler.getTypes(),
                  testCompiler.getMessager(),
                  jarBuilder,
                  AbiGenerationMode.CLASS,
                  additionalOptions.contains("-parameters"),
                  false,
                  null);

          testCompiler.addPostEnterCallback(generator::generate);
          testCompiler.setAllowCompilationErrors(!expectedCompileErrors.isEmpty());
          testCompiler.enter();

          if (expectedCompileErrors.isEmpty()) {
            testCompiler.getClasses().writeToJar(jarBuilder);
            jarBuilder.createJarFile(stubJar.getPath());
          } else {
            List<String> actualCompileErrors =
                testCompiler.getErrorMessages().stream()
                    .map(
                        diagnostic ->
                            diagnostic.substring(diagnostic.lastIndexOf(File.separatorChar) + 1))
                    .collect(Collectors.toList());

            assertEquals(expectedCompileErrors, actualCompileErrors);
          }
        }
        stubJarPath = stubJar;
      } else {
        compileFullJar();
        stubJarPath = StubJarTest.this.createStubJar(fullJarPath);
      }

      return this;
    }

    public Tester compileFullJar() throws IOException {
      File outputDir = temp.newFolder();
      if (language.equals(Language.KOTLIN)) {
        fullJarPath = compileToKotlinJar(classpath, sourceFileName, sourceFileContents, outputDir);
      } else {
        fullJarPath =
            compileToJar(
                ImmutableSortedSet.<Path>naturalOrder()
                    .addAll(classpath)
                    .addAll(universalClasspath)
                    .build(),
                Collections.emptyList(),
                additionalOptions,
                manifest,
                sourceFileName,
                sourceFileContents,
                outputDir);
      }

      return this;
    }

    public Tester addStubJarToClasspath() {
      classpath =
          ImmutableSortedSet.<Path>naturalOrder()
              .addAll(classpath)
              .add(stubJarPath.getPath())
              .build();
      resetActuals();
      return this;
    }

    public Tester addFullJarToClasspath() {
      classpath =
          ImmutableSortedSet.<Path>naturalOrder()
              .addAll(classpath)
              .add(fullJarPath.getPath())
              .build();
      resetActuals();
      return this;
    }

    public Tester addFullJarToClasspathAlways() {
      universalClasspath =
          ImmutableSortedSet.<Path>naturalOrder()
              .addAll(universalClasspath)
              .add(fullJarPath.getPath())
              .build();
      resetActuals();
      return this;
    }

    public void testCanCompile() throws IOException {
      File outputDir = temp.newFolder();
      if (language.equals(Language.KOTLIN)) {
        try (KotlinTestCompiler compiler = new KotlinTestCompiler()) {
          compiler.init();
          compiler.addSourceFileContents(sourceFileName, sourceFileContents);
          compiler.addClasspath(classpath);
          compiler.compile();

          fullJarPath = AbsPath.of(outputDir.toPath()).resolve("output.jar");
          compiler.getClasses().createJar(fullJarPath.getPath(), false);
        }
      } else {
        try (TestCompiler compiler = new TestCompiler()) {
          compiler.init();
          compiler.addCompilerOptions(additionalOptions);
          compiler.addSourceFileContents(sourceFileName, sourceFileContents);
          compiler.addClasspath(universalClasspath);
          compiler.addClasspath(classpath);
          compiler.setSourceLevel(sourceVersion);
          compiler.setTargetLevel(sourceVersion);
          compiler.setProcessors(Collections.emptyList());
          compiler.setAllowCompilationErrors(!expectedCompileErrors.isEmpty());

          compiler.compile();
          if (!expectedCompileErrors.isEmpty()) {
            List<String> actualCompileErrors =
                compiler.getErrorMessages().stream()
                    .map(
                        diagnostic ->
                            diagnostic.substring(diagnostic.lastIndexOf(File.separatorChar) + 1))
                    .collect(Collectors.toList());

            assertEquals(expectedCompileErrors, actualCompileErrors);
          }

          fullJarPath = AbsPath.of(outputDir.toPath()).resolve("output.jar");
          compiler.getClasses().createJar(fullJarPath.getPath(), false);
        }
      }
    }

    public void assertStubJarIsIdentical() throws IOException {
      HashCode originalHash = Files.asByteSource(stubJarPath.toFile()).hash(Hashing.sha1());

      createStubJar();

      assertEquals(originalHash, Files.asByteSource(stubJarPath.toFile()).hash(Hashing.sha1()));
    }

    public void assertStubJarIsDifferent() throws IOException {
      HashCode originalHash = Files.asByteSource(stubJarPath.toFile()).hash(Hashing.sha1());

      createStubJar();

      assertNotEquals(originalHash, Files.asByteSource(stubJarPath.toFile()).hash(Hashing.sha1()));
    }

    @SuppressWarnings("unused")
    public Tester dumpTestCode(boolean includeFullAbi) throws IOException {
      if (includeFullAbi) {
        compileFullJar();
        dumpFullJarAbi();
      }
      createStubJar();
      dumpStubJar();

      String indent = "            ";
      StringBuilder result = new StringBuilder();
      result.append("Test lines:\n");
      result.append("    tester\n");
      result.append("        .setSourceFile(\n");
      result.append(indent);
      result.append('"');
      result.append(sourceFileName);
      for (String sourceLine : sourceFileContents.split("\n")) {
        result.append("\",\n");
        result.append(indent);
        result.append('"');
        result.append(sourceLine.replace("\"", "\\\""));
      }
      result.append("\")\n");
      for (String fileName : actualFullDirectory) {
        if (fileName.endsWith("/") || fileName.equals(JarFile.MANIFEST_NAME)) {
          continue;
        }
        if (includeFullAbi) {
          result.append("        .addExpectedFullAbi(\n");
          result.append(indent);
          result.append('"');
          result.append(fileName, 0, fileName.length() - ".class".length());

          for (String abiLine : actualFullAbis.get(fileName)) {
            result.append("\",\n");
            result.append(indent);
            result.append('"');
            result.append(abiLine.replace("\"", "\\\""));
          }
          result.append("\")\n");
        }

        if (actualStubs.containsKey(fileName)) {
          result.append("        .addExpectedStub(\n");
          result.append(indent);
          result.append('"');
          result.append(fileName, 0, fileName.length() - ".class".length());

          for (String stubLine : actualStubs.get(fileName)) {
            result.append("\",\n");
            result.append(indent);
            result.append('"');
            result.append(stubLine.replace("\"", "\\\""));
          }
          result.append("\")\n");
        }
      }
      result.append("        .createAndCheckStubJar();\n");

      fail(result.toString());
      return this;
    }

    protected void dumpStubJar() throws IOException {
      try (JarFile file = new JarFile(stubJarPath.toFile())) {
        Iterable<JarEntry> entries = file.stream()::iterator;
        for (JarEntry entry : entries) {
          String name = entry.getName();
          if (JarFile.MANIFEST_NAME.equals(name)) {
            actualStubManifest =
                new JarDumper().dumpEntry(file, entry).collect(Collectors.toList());
            continue;
          }
          actualStubDirectory.add(name);
          actualStubs.put(
              name, new JarDumper().dumpEntry(file, entry).collect(Collectors.toList()));
        }
      }
    }

    protected void dumpFullJarAbi() throws IOException {
      try (JarFile file = new JarFile(fullJarPath.toFile())) {
        Iterable<JarEntry> entries = file.stream()::iterator;
        for (JarEntry entry : entries) {
          String name = entry.getName();
          if (JarFile.MANIFEST_NAME.equals(name)) {
            continue;
          }
          actualFullDirectory.add(name);
          actualFullAbis.put(
              name,
              new JarDumper()
                  .setAsmFlags(
                      ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
                  .dumpEntry(file, entry)
                  .collect(Collectors.toList()));
        }
      }
    }
  }
}
